diff --git a/src/core/dataframe/DataFrame.js b/src/core/dataframe/DataFrame.js index cf59d0c..b51bdf6 100644 --- a/src/core/dataframe/DataFrame.js +++ b/src/core/dataframe/DataFrame.js @@ -1,7 +1,5 @@ // src/core/dataframe/DataFrame.js import { Series } from './Series.js'; -import { VectorFactory } from '../storage/VectorFactory.js'; -import { shouldUseArrow } from '../strategy/shouldUseArrow.js'; export class DataFrame { /** @@ -15,80 +13,73 @@ export class DataFrame { this._order = Object.keys(data); for (const name of this._order) { - // If data is already a Series, use it directly - if (data[name] instanceof Series) { - this._columns[name] = data[name]; - } else { - // Otherwise create a new Series - this._columns[name] = new Series(data[name], { - name, - ...opts, - }); - } + // Re-use Series or wrap raw data + this._columns[name] = + data[name] instanceof Series + ? data[name] + : new Series(data[name], { name, ...opts }); } Object.freeze(this._order); + + /* -------------------------------------------------- * + * Internal helper (used by tests / plugins) * + * -------------------------------------------------- */ + Object.defineProperty(this, 'frame', { + enumerable: false, + configurable: false, + value: { + /** + * low-level vector getter (internal) + * @param n + */ + getColumn: (n) => this._columns[n]?.vector, + }, + }); } /* ------------------------------------------------------------------ * - * Factories (static methods) * + * Factory helpers * * ------------------------------------------------------------------ */ - static create(cols, opts = {}) { - return new DataFrame(cols, opts); - } - static fromColumns(cols, opts = {}) { - return new DataFrame(cols, opts); - } + static create = (cols, opts = {}) => new DataFrame(cols, opts); + static fromColumns = (cols, opts = {}) => new DataFrame(cols, opts); - /** - * Array of objects → DataFrame - * @param rows - * @param opts - */ static fromRows(rows = [], opts = {}) { if (!rows.length) return new DataFrame({}, opts); - const keys = Object.keys(rows[0] || {}); const cols = {}; - for (const k of keys) cols[k] = rows.map((r) => r[k]); + for (const k of Object.keys(rows[0])) cols[k] = rows.map((r) => r[k]); return new DataFrame(cols, opts); } - /** - * Apache Arrow Table → DataFrame - * @param table - */ static fromArrow(table) { const cols = {}; - for (const field of table.schema.fields) { - cols[field.name] = table.getColumn(field.name).toArray(); - } + for (const f of table.schema.fields) + cols[f.name] = table.getColumn(f.name).toArray(); return new DataFrame(cols, { preferArrow: true }); } /* ------------------------------------------------------------------ * - * Data Export * + * Export helpers * * ------------------------------------------------------------------ */ - /** DataFrame → { col: Array } */ toColumns() { const out = {}; - for (const name of this._order) out[name] = this._columns[name].toArray(); + for (const n of this._order) out[n] = this._columns[n].toArray(); return out; } - /** DataFrame → Arrow.Table (if lib is available) */ - toArrow() { - const { tableFromArrays } = require('apache-arrow'); + async toArrow() { + const { tableFromArrays } = await import('apache-arrow'); const arrays = {}; - for (const name of this._order) { - const vec = this._columns[name].vector; - arrays[name] = vec._arrow ?? vec._data; // ArrowVector | TypedArray + for (const n of this._order) { + const v = this._columns[n].vector; + arrays[n] = v._arrow ?? v._data; // ArrowVector | TypedArray } return tableFromArrays(arrays); } /* ------------------------------------------------------------------ * - * Getters and quick accessors * + * Accessors * * ------------------------------------------------------------------ */ get rowCount() { @@ -98,166 +89,112 @@ export class DataFrame { return [...this._order]; } - col(name) { - return this._columns[name]; - } - sum(name) { - return this.col(name).sum(); - } + col = (n) => this._columns[n]; + sum = (n) => this.col(n).sum(); + /** + * low-level vector getter + * @param n + */ + getVector = (n) => this._columns[n]?.vector; /* ------------------------------------------------------------------ * - * DataFrame operations * + * Column-level helpers (select / drop / assign) * * ------------------------------------------------------------------ */ - /** - * Returns a new DataFrame with a subset of columns - * @param names - */ select(names) { - const subset = {}; - for (const n of names) subset[n] = this._columns[n].toArray(); - return new DataFrame(subset); + const cols = {}; + for (const n of names) cols[n] = this._columns[n].toArray(); + return new DataFrame(cols); } - /** - * Remove specified columns - * @param names - */ drop(names) { - const keep = {}; - for (const n of this._order) - if (!names.includes(n)) keep[n] = this._columns[n].toArray(); - return new DataFrame(keep); + const keep = this.columns.filter((c) => !names.includes(c)); + return this.select(keep); } - /** - * Add / replace columns. - * @param {Record} obj - */ assign(obj) { - const merged = this.toColumns(); // existing columns - for (const [k, v] of Object.entries(obj)) { + const merged = this.toColumns(); + for (const [k, v] of Object.entries(obj)) merged[k] = v instanceof Series ? v.toArray() : v; - } return new DataFrame(merged); } /* ------------------------------------------------------------------ * - * Convert to array of rows (row-wise) * + * Conversion to row array * * ------------------------------------------------------------------ */ toArray() { - // If there are no columns, return an empty array if (!this._order.length) return []; - - const out = []; const len = this.rowCount; - for (let i = 0; i < len; i++) { - const row = {}; - for (const name of this._order) { - row[name] = this._columns[name].get(i); - } - out.push(row); - } - return out; + const rows = Array.from({ length: len }, (_, i) => { + const r = {}; + for (const n of this._order) r[n] = this._columns[n].get(i); + return r; + }); + return rows; } /* ------------------------------------------------------------------ * - * Lazy API * + * Lazy & meta * * ------------------------------------------------------------------ */ - /** @returns {Promise} */ - lazy() { - return import('../lazy/LazyFrame.js').then((m) => - m.LazyFrame.fromDataFrame(this), - ); + lazy = () => + import('../lazy/LazyFrame.js').then((m) => m.LazyFrame.fromDataFrame(this)); + + setMeta = (m) => ((this._meta = m), this); + getMeta = () => this._meta ?? {}; + + async optimizeFor(op) { + const { switchStorage } = await import('../strategy/storageStrategy.js'); + return switchStorage(this, op); } /* ------------------------------------------------------------------ * - * Visualization * + * Simple render helpers * * ------------------------------------------------------------------ */ - /** - * Output as HTML table (for Jupyter-like UI) - * @returns {string} HTML string - */ toHTML() { - const headers = this.columns.map((name) => `${name}`).join(''); - const rows = this.toArray() - .map((row) => { - const cells = this.columns - .map((name) => `${row[name]}`) - .join(''); - return `${cells}`; - }) + const head = this.columns.map((n) => `${n}`).join(''); + const body = this.toArray() + .map( + (row) => + '' + + this.columns.map((n) => `${row[n]}`).join('') + + '', + ) .join(''); - return `${headers}${rows}
`; + return `${head}${body}
`; } - /** - * Output as Markdown table (for .md reports) - * @returns {string} Markdown table string - */ toMarkdown() { const header = '| ' + this.columns.join(' | ') + ' |'; const divider = '| ' + this.columns.map(() => '---').join(' | ') + ' |'; const rows = this.toArray().map( - (row) => '| ' + this.columns.map((name) => row[name]).join(' | ') + ' |', + (r) => '| ' + this.columns.map((n) => r[n]).join(' | ') + ' |', ); return [header, divider, ...rows].join('\n'); } - /* ------------------------------------------------------------------ * - * DataFrame operations * + * Meta & storage helpers * * ------------------------------------------------------------------ */ /** - * Select subset of columns (select) - * @param names - */ - select(names) { - const selected = {}; - for (const name of names) { - selected[name] = this.col(name).toArray(); - } - return new DataFrame(selected); - } - - /** - * Remove specified columns (drop) - * @param names + * Set metadata for the DataFrame + * @param {any} m - Metadata to set + * @returns {DataFrame} - This DataFrame for chaining */ - drop(names) { - const remaining = this.columns.filter((name) => !names.includes(name)); - return this.select(remaining); - } + setMeta = (m) => ((this._meta = m), this); /** - * Add or update columns - * @param obj + * Get metadata for the DataFrame + * @returns {any} - DataFrame metadata or empty object if not set */ - assign(obj) { - const updated = this.toColumns(); - for (const key in obj) updated[key] = obj[key]; - return new DataFrame(updated); - } - - /** - * Insert metadata - * @param meta - */ - setMeta(meta) { - this._meta = meta; - return this; - } - - getMeta() { - return this._meta ?? {}; - } + getMeta = () => this._meta ?? {}; /** * Optimize storage for operation - * @param op + * @param {string} op - Operation to optimize for + * @returns {Promise} - Optimized DataFrame */ async optimizeFor(op) { const { switchStorage } = await import('../strategy/storageStrategy.js'); diff --git a/src/methods/dataframe/display/display.js b/src/methods/dataframe/display/display.js new file mode 100644 index 0000000..2fb26a0 --- /dev/null +++ b/src/methods/dataframe/display/display.js @@ -0,0 +1,21 @@ +/** + * Display DataFrame in browser environment + * @returns {Function} Function that takes a frame and displays it in browser + */ +import { display as webDisplay } from '../../../display/web/html.js'; + +/** + * Factory function that returns a display function for DataFrame + * @returns {Function} Function that takes a frame and displays it in browser + */ +export const display = + () => + (frame, options = {}) => { + // Use the existing display function from display/web/html.js + webDisplay(frame, options); + + // Return the frame for method chaining + return frame; + }; + +export default display; diff --git a/src/methods/dataframe/display/index.js b/src/methods/dataframe/display/index.js new file mode 100644 index 0000000..3e1827d --- /dev/null +++ b/src/methods/dataframe/display/index.js @@ -0,0 +1,11 @@ +/** + * Index file for DataFrame display methods + */ +export { print } from './print.js'; +export { toHTML } from './toHTML.js'; +export { display } from './display.js'; +export { renderTo } from './renderTo.js'; +export { toJupyter, registerJupyterDisplay } from './toJupyter.js'; + +// Export the register function as default +export { default } from './register.js'; diff --git a/src/methods/dataframe/display/print.js b/src/methods/dataframe/display/print.js new file mode 100644 index 0000000..24cb6de --- /dev/null +++ b/src/methods/dataframe/display/print.js @@ -0,0 +1,128 @@ +/** + * Print DataFrame as a formatted table in the console + * @returns {Function} Function that takes a frame and prints it to console + */ + +/** + * Factory function that returns a print function for DataFrame + * @returns {Function} Function that takes a frame and prints it to console + */ +export const print = + () => + (frame, maxRows = 10, maxCols = Infinity) => { + // Create a formatted table representation + const table = formatDataFrameTable(frame, maxRows, maxCols); + + // Print the table + console.log(table); + + // Return the frame for method chaining + return frame; + }; + +/** + * Format a DataFrame as a table string + * @param {Object} frame - DataFrame object + * @param {number} maxRows - Maximum number of rows to display + * @param {number} maxCols - Maximum number of columns to display + * @returns {string} - Formatted table string + */ +function formatDataFrameTable(frame, maxRows = 10, maxCols = Infinity) { + // Handle case when frame is undefined or doesn't have expected structure + if (!frame || typeof frame !== 'object') { + return 'Invalid DataFrame'; + } + + // Extract columns and data from the DataFrame + const columns = frame._order || []; + const data = {}; + let rowCount = 0; + + if (frame._columns) { + // Extract data from DataFrame's Series objects + rowCount = + columns.length > 0 && frame._columns[columns[0]] + ? frame._columns[columns[0]].length + : 0; + + for (const col of columns) { + const series = frame._columns[col]; + if (series) { + // Handle Series objects which may have vector property + if (series.vector && series.vector._data) { + data[col] = Array.from(series.vector._data); + } else if (series.toArray) { + data[col] = series.toArray(); + } else if (Array.isArray(series)) { + data[col] = series; + } else { + data[col] = []; + } + } else { + data[col] = []; + } + } + } + + // If no columns or data found, return empty message + if (columns.length === 0) { + return 'Empty DataFrame'; + } + + // Limit columns if needed + const displayColumns = + maxCols < columns.length ? columns.slice(0, maxCols) : columns; + + // Determine rows to display + const displayRows = Math.min(rowCount, maxRows); + + // Create a table representation + let table = 'DataFrame Table:\n'; + + // Add header + table += displayColumns.join(' | ') + '\n'; + table += + displayColumns.map((col) => '-'.repeat(col.length)).join('-+-') + '\n'; + + // Add data rows + for (let i = 0; i < displayRows; i++) { + const rowValues = displayColumns.map((col) => { + const value = + data[col] && i < data[col].length ? data[col][i] : undefined; + return formatValue(value); + }); + table += rowValues.join(' | ') + '\n'; + } + + // Add message about additional rows if needed + if (rowCount > displayRows) { + table += `... ${rowCount - displayRows} more rows ...\n`; + } + + // Add message about additional columns if needed + if (columns.length > displayColumns.length) { + table += `... and ${columns.length - displayColumns.length} more columns ...\n`; + } + + // Add dimensions + table += `[${rowCount} rows x ${columns.length} columns]`; + + return table; +} + +/** + * Format a value for display in the table + * @param {*} value - The value to format + * @returns {string} - Formatted string representation + */ +function formatValue(value) { + if (value === null) return 'null'; + if (value === undefined) return 'undefined'; + if (typeof value === 'number' && isNaN(value)) return 'NaN'; + if (typeof value === 'object' && value instanceof Date) { + return value.toISOString(); + } + return String(value); +} + +export default print; diff --git a/src/methods/dataframe/display/renderTo.js b/src/methods/dataframe/display/renderTo.js new file mode 100644 index 0000000..23a9ca8 --- /dev/null +++ b/src/methods/dataframe/display/renderTo.js @@ -0,0 +1,21 @@ +/** + * Render DataFrame to a specified DOM element + * @returns {Function} Function that takes a frame and renders it to a DOM element + */ +import { renderTo as webRenderTo } from '../../../display/web/html.js'; + +/** + * Factory function that returns a renderTo function for DataFrame + * @returns {Function} Function that takes a frame and renders it to a DOM element + */ +export const renderTo = + () => + (frame, element, options = {}) => { + // Use the existing renderTo function from display/web/html.js + webRenderTo(frame, element, options); + + // Return the frame for method chaining + return frame; + }; + +export default renderTo; diff --git a/src/methods/dataframe/display/toHTML.js b/src/methods/dataframe/display/toHTML.js new file mode 100644 index 0000000..6435491 --- /dev/null +++ b/src/methods/dataframe/display/toHTML.js @@ -0,0 +1,17 @@ +/** + * Convert DataFrame to HTML table + * @returns {Function} Function that takes a frame and returns HTML string + */ +import { toHTML as webToHTML } from '../../../display/web/html.js'; + +/** + * Factory function that returns a toHTML function for DataFrame + * @returns {Function} Function that takes a frame and returns HTML string + */ +export const toHTML = + () => + (frame, options = {}) => + // Use the existing toHTML function from display/web/html.js + webToHTML(frame, options); + +export default toHTML; diff --git a/src/methods/dataframe/display/toJupyter.js b/src/methods/dataframe/display/toJupyter.js new file mode 100644 index 0000000..93c8e24 --- /dev/null +++ b/src/methods/dataframe/display/toJupyter.js @@ -0,0 +1,29 @@ +/** + * Convert DataFrame to Jupyter notebook compatible representation + * @returns {Function} Function that takes a frame and returns Jupyter display object + */ +import { + toJupyter as jupyterToJupyter, + registerJupyterDisplay as jupyterRegister, +} from '../../../display/web/jupyter.js'; + +/** + * Factory function that returns a toJupyter function for DataFrame + * @returns {Function} Function that takes a frame and returns Jupyter display object + */ +export const toJupyter = + () => + (frame, options = {}) => + // Use the existing toJupyter function from display/web/jupyter.js + jupyterToJupyter(frame, options); + +/** + * Register special display methods for Jupyter notebook + * @param {Class} DataFrame - DataFrame class to extend + */ +export const registerJupyterDisplay = (DataFrame) => { + // Use the existing registerJupyterDisplay function from display/web/jupyter.js + jupyterRegister(DataFrame); +}; + +export default toJupyter; diff --git a/test/methods/dataframe/display/display.test.js b/test/methods/dataframe/display/display.test.js new file mode 100644 index 0000000..2d51e64 --- /dev/null +++ b/test/methods/dataframe/display/display.test.js @@ -0,0 +1,70 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { DataFrame } from '../../../../src/core/dataframe/DataFrame.js'; +import { display } from '../../../../src/methods/dataframe/display/display.js'; + +import { + testWithBothStorageTypes, + createDataFrameWithStorage, +} from '../../../utils/storageTestUtils.js'; + +// Mock the module +vi.mock('../../../../src/display/web/html.js', () => ({ + display: vi.fn(), +})); + +// Import the mocked function after mocking +import { display as mockWebDisplay } from '../../../../src/display/web/html.js'; + +describe('DataFrame display method', () => { + // Reset mock before each test + beforeEach(() => { + mockWebDisplay.mockReset(); + }); + + // Run tests with both storage types + testWithBothStorageTypes((storageType) => { + describe(`with ${storageType} storage`, () => { + // Create test data frame with people data for better readability in tests + const testData = [ + { name: 'Alice', age: 25, city: 'New York' }, + { name: 'Bob', age: 30, city: 'Boston' }, + { name: 'Charlie', age: 35, city: 'Chicago' }, + ]; + + // Create DataFrame with the specified storage type + const df = createDataFrameWithStorage(DataFrame, testData, storageType); + + it('should call the web display function with the frame', () => { + // Call display function directly + const displayFn = display(); + displayFn(df); + + // Check that the web display function was called with the frame + expect(mockWebDisplay).toHaveBeenCalledWith(df, expect.any(Object)); + }); + + it('should return the frame for method chaining', () => { + // Call display function and check the return value + const displayFn = display(); + const result = displayFn(df); + + // Check that the function returns the frame + expect(result).toBe(df); + }); + + it('should pass options to the web display function', () => { + // Call display function with options + const displayFn = display(); + const options = { + maxRows: 5, + maxCols: 2, + className: 'custom-table', + }; + displayFn(df, options); + + // Check that the web display function was called with the options + expect(mockWebDisplay).toHaveBeenCalledWith(df, options); + }); + }); + }); +}); diff --git a/test/methods/dataframe/display/print.test.js b/test/methods/dataframe/display/print.test.js index 1f4372b..7b31ba2 100644 --- a/test/methods/dataframe/display/print.test.js +++ b/test/methods/dataframe/display/print.test.js @@ -8,7 +8,7 @@ import { } from '../../../utils/storageTestUtils.js'; // Test data to be used in all tests -const testData = [ +const sampleData = [ { value: 10, category: 'A', mixed: '20' }, { value: 20, category: 'B', mixed: 30 }, { value: 30, category: 'A', mixed: null }, @@ -20,10 +20,7 @@ describe('DataFrame print method', () => { // Run tests with both storage types testWithBothStorageTypes((storageType) => { describe(`with ${storageType} storage`, () => { - // Create DataFrame with the specified storage type - const df = createDataFrameWithStorage(DataFrame, testData, storageType); - - // Create test data frame + // Create test data frame with people data for better readability in tests const testData = [ { name: 'Alice', age: 25, city: 'New York' }, { name: 'Bob', age: 30, city: 'Boston' }, @@ -32,7 +29,8 @@ describe('DataFrame print method', () => { { name: 'Eve', age: 45, city: 'El Paso' }, ]; - // df created above using createDataFrameWithStorage + // Create DataFrame with the specified storage type + const df = createDataFrameWithStorage(DataFrame, testData, storageType); it('should format data as a table string', () => { // Mock console.log to check output @@ -42,7 +40,7 @@ describe('DataFrame print method', () => { // Call print function directly const printFn = print(); - printFn(df._frame); + printFn(df); // Check that console.log was called expect(consoleSpy).toHaveBeenCalled(); @@ -65,17 +63,17 @@ describe('DataFrame print method', () => { }); it('should return the frame for method chaining', () => { - // Mock console.log + // Mock console.log to prevent output const consoleSpy = vi .spyOn(console, 'log') .mockImplementation(() => {}); - // Call print function directly + // Call print function and check the return value const printFn = print(); - const result = printFn(df._frame); + const result = printFn(df); // Check that the function returns the frame - expect(result).toBe(df._frame); + expect(result).toBe(df); // Restore console.log consoleSpy.mockRestore(); @@ -88,7 +86,11 @@ describe('DataFrame print method', () => { value: i * 10, })); - const largeDf = DataFrame.create(largeData); + const largeDf = createDataFrameWithStorage( + DataFrame, + largeData, + storageType, + ); // Mock console.log const consoleSpy = vi @@ -97,7 +99,7 @@ describe('DataFrame print method', () => { // Call print function with row limit const printFn = print(); - printFn(largeDf._frame, 5); + printFn(largeDf, 5); // Get the output const output = consoleSpy.mock.calls[0][0]; @@ -111,11 +113,19 @@ describe('DataFrame print method', () => { it('should respect cols limit', () => { // Create a frame with many columns - const wideData = [ - { col1: 1, col2: 2, col3: 3, col4: 4, col5: 5, col6: 6 }, - ]; - - const wideDf = DataFrame.create(wideData); + const wideData = { + col1: [1, 2, 3], + col2: [4, 5, 6], + col3: [7, 8, 9], + col4: [10, 11, 12], + col5: [13, 14, 15], + }; + + const wideDf = createDataFrameWithStorage( + DataFrame, + wideData, + storageType, + ); // Mock console.log const consoleSpy = vi @@ -124,7 +134,7 @@ describe('DataFrame print method', () => { // Call print function with column limit const printFn = print(); - printFn(wideDf._frame, undefined, 3); + printFn(wideDf, Infinity, 2); // Get the output const output = consoleSpy.mock.calls[0][0]; diff --git a/test/methods/dataframe/display/renderTo.test.js b/test/methods/dataframe/display/renderTo.test.js new file mode 100644 index 0000000..a467089 --- /dev/null +++ b/test/methods/dataframe/display/renderTo.test.js @@ -0,0 +1,77 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { DataFrame } from '../../../../src/core/dataframe/DataFrame.js'; +import { renderTo } from '../../../../src/methods/dataframe/display/renderTo.js'; + +import { + testWithBothStorageTypes, + createDataFrameWithStorage, +} from '../../../utils/storageTestUtils.js'; + +// Mock the module +vi.mock('../../../../src/display/web/html.js', () => ({ + renderTo: vi.fn(), +})); + +// Import the mocked function after mocking +import { renderTo as mockWebRenderTo } from '../../../../src/display/web/html.js'; + +describe('DataFrame renderTo method', () => { + // Reset mock before each test + beforeEach(() => { + mockWebRenderTo.mockReset(); + }); + + // Run tests with both storage types + testWithBothStorageTypes((storageType) => { + describe(`with ${storageType} storage`, () => { + // Create test data frame with people data for better readability in tests + const testData = [ + { name: 'Alice', age: 25, city: 'New York' }, + { name: 'Bob', age: 30, city: 'Boston' }, + { name: 'Charlie', age: 35, city: 'Chicago' }, + ]; + + // Create DataFrame with the specified storage type + const df = createDataFrameWithStorage(DataFrame, testData, storageType); + + // Mock DOM element + const mockElement = { id: 'test-element' }; + + it('should call the web renderTo function with the frame and element', () => { + // Call renderTo function directly + const renderToFn = renderTo(); + renderToFn(df, mockElement); + + // Check that the web renderTo function was called with the frame and element + expect(mockWebRenderTo).toHaveBeenCalledWith( + df, + mockElement, + expect.any(Object), + ); + }); + + it('should return the frame for method chaining', () => { + // Call renderTo function and check the return value + const renderToFn = renderTo(); + const result = renderToFn(df, mockElement); + + // Check that the function returns the frame + expect(result).toBe(df); + }); + + it('should pass options to the web renderTo function', () => { + // Call renderTo function with options + const renderToFn = renderTo(); + const options = { + maxRows: 5, + maxCols: 2, + className: 'custom-table', + }; + renderToFn(df, mockElement, options); + + // Check that the web renderTo function was called with the options + expect(mockWebRenderTo).toHaveBeenCalledWith(df, mockElement, options); + }); + }); + }); +}); diff --git a/test/methods/dataframe/display/toHTML.test.js b/test/methods/dataframe/display/toHTML.test.js new file mode 100644 index 0000000..9ecce53 --- /dev/null +++ b/test/methods/dataframe/display/toHTML.test.js @@ -0,0 +1,69 @@ +import { describe, it, expect, vi } from 'vitest'; +import { DataFrame } from '../../../../src/core/dataframe/DataFrame.js'; +import { toHTML } from '../../../../src/methods/dataframe/display/toHTML.js'; + +import { + testWithBothStorageTypes, + createDataFrameWithStorage, +} from '../../../utils/storageTestUtils.js'; + +describe('DataFrame toHTML method', () => { + // Run tests with both storage types + testWithBothStorageTypes((storageType) => { + describe(`with ${storageType} storage`, () => { + // Create test data frame with people data for better readability in tests + const testData = [ + { name: 'Alice', age: 25, city: 'New York' }, + { name: 'Bob', age: 30, city: 'Boston' }, + { name: 'Charlie', age: 35, city: 'Chicago' }, + ]; + + // Create DataFrame with the specified storage type + const df = createDataFrameWithStorage(DataFrame, testData, storageType); + + it('should convert DataFrame to HTML string', () => { + // Call toHTML function directly + const toHTMLFn = toHTML(); + const html = toHTMLFn(df); + + // Check that the result is a string + expect(typeof html).toBe('string'); + + // Check that the HTML contains expected elements + expect(html).toContain(''); + + // Check that the HTML contains style information + expect(html).toContain('