530 lines
15 KiB
TypeScript
530 lines
15 KiB
TypeScript
import React from 'react';
|
|
import { act } from '@testing-library/react';
|
|
import {
|
|
HotTable
|
|
} from '../src/hotTable';
|
|
import {
|
|
createSpreadsheetData,
|
|
IndividualPropsWrapper,
|
|
mockElementDimensions,
|
|
RendererComponent,
|
|
EditorComponent,
|
|
SingleObjectWrapper,
|
|
sleep,
|
|
simulateKeyboardEvent,
|
|
simulateMouseEvent,
|
|
mountComponent,
|
|
mountComponentWithRef
|
|
} from './_helpers';
|
|
|
|
describe('Handsontable initialization', () => {
|
|
it('should render Handsontable when using the HotTable component', async () => {
|
|
const hotInstance = mountComponentWithRef((
|
|
<HotTable
|
|
id="test-hot"
|
|
data={[[2]]}
|
|
licenseKey="non-commercial-and-evaluation"
|
|
/>
|
|
)).hotInstance;
|
|
|
|
expect(hotInstance).not.toBe(null);
|
|
expect(hotInstance).not.toBe(void 0);
|
|
|
|
expect(hotInstance.rootElement.id).toEqual('test-hot');
|
|
});
|
|
|
|
it('should pass the provided properties to the Handsontable instance', async () => {
|
|
const hotInstance = mountComponentWithRef((
|
|
<HotTable
|
|
id="test-hot"
|
|
contextMenu={true}
|
|
rowHeaders={true}
|
|
colHeaders={true}
|
|
data={[[2]]}
|
|
licenseKey="non-commercial-and-evaluation"/>
|
|
)).hotInstance;
|
|
|
|
expect(hotInstance.rootElement.id).toBe('test-hot');
|
|
expect(hotInstance.getSettings().contextMenu).toBe(true);
|
|
expect(hotInstance.getSettings().rowHeaders).toBe(true);
|
|
expect(hotInstance.getSettings().colHeaders).toBe(true);
|
|
expect(JSON.stringify(hotInstance.getData())).toEqual('[[2]]');
|
|
});
|
|
});
|
|
|
|
describe('Updating the Handsontable settings', () => {
|
|
it('should call the updateSettings method of Handsontable, when the component properties get updated (when providing properties individually)', async () => {
|
|
const componentInstance = mountComponentWithRef((
|
|
<IndividualPropsWrapper/>
|
|
));
|
|
|
|
const hotInstance = componentInstance.hotTable.hotInstance;
|
|
let updateSettingsCount = 0;
|
|
|
|
|
|
hotInstance.addHook('afterUpdateSettings', () => {
|
|
updateSettingsCount++;
|
|
});
|
|
|
|
await sleep(300);
|
|
|
|
await act(async () => {
|
|
componentInstance.setState({
|
|
hotSettings: {
|
|
data: [[2]],
|
|
contextMenu: true,
|
|
readOnly: true
|
|
}
|
|
});
|
|
});
|
|
|
|
expect(updateSettingsCount).toEqual(1);
|
|
});
|
|
|
|
it('should call the updateSettings method of Handsontable, when the component properties get updated (when providing properties as a single settings object)', async () => {
|
|
const componentInstance = mountComponentWithRef((
|
|
<SingleObjectWrapper/>
|
|
));
|
|
|
|
const hotInstance = componentInstance.hotTable.hotInstance;
|
|
let updateSettingsCount = 0;
|
|
|
|
hotInstance.addHook('afterUpdateSettings', () => {
|
|
updateSettingsCount++;
|
|
});
|
|
|
|
await sleep(300);
|
|
|
|
await act(async () => {
|
|
componentInstance.setState({
|
|
hotSettings: {
|
|
data: [[2]],
|
|
contextMenu: true,
|
|
readOnly: true
|
|
}
|
|
});
|
|
});
|
|
|
|
expect(updateSettingsCount).toEqual(1);
|
|
});
|
|
|
|
it('should update the Handsontable options, when the component properties get updated (when providing properties individually)', async () => {
|
|
const componentInstance = mountComponentWithRef((
|
|
<IndividualPropsWrapper/>
|
|
));
|
|
|
|
const hotInstance = componentInstance.hotTable.hotInstance;
|
|
|
|
expect(hotInstance.getSettings().contextMenu).toEqual(void 0);
|
|
expect(hotInstance.getSettings().readOnly).toEqual(false);
|
|
expect(JSON.stringify(hotInstance.getSettings().data)).toEqual('[[null,null,null,null,null],[null,null,null,null,null],[null,null,null,null,null],[null,null,null,null,null],[null,null,null,null,null]]');
|
|
|
|
await sleep(300);
|
|
|
|
await act(async () => {
|
|
componentInstance.setState({
|
|
hotSettings: {
|
|
data: [[2]],
|
|
contextMenu: true,
|
|
readOnly: true
|
|
}
|
|
});
|
|
});
|
|
|
|
expect(hotInstance.getSettings().contextMenu).toBe(true);
|
|
expect(hotInstance.getSettings().readOnly).toBe(true);
|
|
expect(JSON.stringify(hotInstance.getSettings().data)).toEqual('[[2]]');
|
|
});
|
|
|
|
it('should update the Handsontable options, when the component properties get updated (when providing properties as a single settings object)', async () => {
|
|
const componentInstance = mountComponentWithRef((
|
|
<SingleObjectWrapper/>
|
|
));
|
|
|
|
const hotInstance = componentInstance.hotTable.hotInstance;
|
|
|
|
expect(hotInstance.getSettings().contextMenu).toEqual(void 0);
|
|
expect(hotInstance.getSettings().readOnly).toEqual(false);
|
|
expect(JSON.stringify(hotInstance.getSettings().data)).toEqual('[[null,null,null,null,null],[null,null,null,null,null],[null,null,null,null,null],[null,null,null,null,null],[null,null,null,null,null]]');
|
|
|
|
await sleep(300);
|
|
|
|
await act(async () => {
|
|
componentInstance.setState({
|
|
hotSettings: {
|
|
data: [[2]],
|
|
contextMenu: true,
|
|
readOnly: true
|
|
}
|
|
});
|
|
});
|
|
|
|
expect(hotInstance.getSettings().contextMenu).toBe(true);
|
|
expect(hotInstance.getSettings().readOnly).toBe(true);
|
|
expect(JSON.stringify(hotInstance.getSettings().data)).toEqual('[[2]]');
|
|
});
|
|
|
|
it('should throw an error when trying to update init-only settings after inializing the component', async () => {
|
|
console.error = jasmine.createSpy('error');
|
|
|
|
let updateState = null;
|
|
|
|
function ExampleComponent() {
|
|
const [renderAllRows, setRenderAllRows] = React.useState(false);
|
|
|
|
(updateState as any) = setRenderAllRows;
|
|
|
|
return (
|
|
<>
|
|
<HotTable licenseKey="non-commercial-and-evaluation"
|
|
id="test-hot"
|
|
data={[[1]]}
|
|
renderAllRows={renderAllRows}
|
|
></HotTable>
|
|
</>
|
|
)
|
|
}
|
|
|
|
mountComponent((
|
|
<ExampleComponent/>
|
|
));
|
|
|
|
const updateStateAndRender = () => act(() => (updateState as any)(true));
|
|
|
|
await expect(updateStateAndRender).toThrowError(
|
|
'The `renderAllRows` option can not be updated after the Handsontable is initialized.'
|
|
);
|
|
|
|
expect(console.error).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should NOT throw an error when trying to update settings after inializing the component if the other settings' +
|
|
'contain init-only entries', async () => {
|
|
console.error = jasmine.createSpy('error');
|
|
|
|
let updateState = null;
|
|
|
|
function ExampleComponent() {
|
|
const [rowHeaders, setRowHeaders] = React.useState(false);
|
|
|
|
(updateState as any) = setRowHeaders;
|
|
|
|
return (
|
|
<>
|
|
<HotTable licenseKey="non-commercial-and-evaluation"
|
|
id="test-hot"
|
|
data={[[1]]}
|
|
rowHeaders={rowHeaders}
|
|
renderAllRows={true}
|
|
></HotTable>
|
|
</>
|
|
)
|
|
}
|
|
|
|
mountComponent((
|
|
<ExampleComponent/>
|
|
));
|
|
|
|
const updateStateAndRender = () => act(() => (updateState as any)(true));
|
|
|
|
await expect(updateStateAndRender).not.toThrowError();
|
|
|
|
expect(console.error).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should NOT throw an error when definiting init-only settings, without updating them afterwards', async () => {
|
|
console.error = jasmine.createSpy('error');
|
|
|
|
function ExampleComponent() {
|
|
return (
|
|
<>
|
|
<HotTable licenseKey="non-commercial-and-evaluation"
|
|
id="test-hot"
|
|
data={[[1]]}
|
|
renderAllRows={true}
|
|
renderAllColumns={true}
|
|
ariaTags={true}
|
|
layoutDirection={"rtl"}
|
|
></HotTable>
|
|
</>
|
|
)
|
|
}
|
|
|
|
mountComponent((
|
|
<ExampleComponent/>
|
|
));
|
|
|
|
expect(console.error).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('Renderer configuration using React components', () => {
|
|
it('should use the renderer component as Handsontable renderer, when it\'s nested under HotTable and assigned the \'hot-renderer\' attribute', async () => {
|
|
const hotInstance = mountComponentWithRef((
|
|
<HotTable licenseKey="non-commercial-and-evaluation"
|
|
id="test-hot"
|
|
data={createSpreadsheetData(100, 100)}
|
|
width={300}
|
|
height={300}
|
|
rowHeights={23}
|
|
colWidths={50}
|
|
autoRowSize={false}
|
|
autoColumnSize={false}
|
|
init={function () {
|
|
mockElementDimensions(this.rootElement, 300, 300);
|
|
}}>
|
|
<RendererComponent hot-renderer></RendererComponent>
|
|
</HotTable>
|
|
)).hotInstance;
|
|
|
|
expect(hotInstance.getCell(0, 0).innerHTML).toEqual('<div>value: A1</div>');
|
|
|
|
await act(async() => {
|
|
hotInstance.scrollViewportTo({
|
|
row: 99,
|
|
col: 0,
|
|
});
|
|
// For some reason it needs another render
|
|
hotInstance.render();
|
|
});
|
|
|
|
await sleep(100);
|
|
|
|
expect(hotInstance.getCell(99, 1).innerHTML).toEqual('<div>value: B100</div>');
|
|
|
|
await act(async() => {
|
|
hotInstance.scrollViewportTo({
|
|
row: 99,
|
|
col: 99,
|
|
});
|
|
hotInstance.render();
|
|
});
|
|
|
|
await sleep(100);
|
|
|
|
expect(hotInstance.getCell(99, 99).innerHTML).toEqual('<div>value: CV100</div>');
|
|
});
|
|
});
|
|
|
|
describe('Editor configuration using React components', () => {
|
|
it('should use the editor component as Handsontable editor and mount it in the root tree of the document', async () => {
|
|
mountComponentWithRef((
|
|
<HotTable licenseKey="non-commercial-and-evaluation"
|
|
id="test-hot"
|
|
data={createSpreadsheetData(3, 3)}
|
|
width={300}
|
|
height={300}
|
|
rowHeights={23}
|
|
colWidths={50}
|
|
init={function () {
|
|
mockElementDimensions(this.rootElement, 300, 300);
|
|
}}>
|
|
<EditorComponent hot-editor></EditorComponent>
|
|
</HotTable>
|
|
)).hotInstance;
|
|
|
|
const editorElement = document.querySelector('#editorComponentContainer');
|
|
|
|
expect(editorElement.parentElement.parentElement).toBe(document.body);
|
|
});
|
|
|
|
it('should use the editor component as Handsontable editor, when it\'s nested under HotTable and assigned the \'hot-editor\' attribute', async () => {
|
|
const hotInstance = mountComponentWithRef((
|
|
<HotTable licenseKey="non-commercial-and-evaluation"
|
|
id="test-hot"
|
|
data={createSpreadsheetData(3, 3)}
|
|
width={300}
|
|
height={300}
|
|
rowHeights={23}
|
|
colWidths={50}
|
|
init={function () {
|
|
mockElementDimensions(this.rootElement, 300, 300);
|
|
}}>
|
|
<EditorComponent hot-editor></EditorComponent>
|
|
</HotTable>
|
|
)).hotInstance;
|
|
|
|
expect((document.querySelector('#editorComponentContainer') as any).style.display).toEqual('none');
|
|
|
|
await act(async () => {
|
|
hotInstance.selectCell(0, 0);
|
|
simulateKeyboardEvent('keydown', 13);
|
|
});
|
|
|
|
expect((document.querySelector('#editorComponentContainer') as any).style.display).toEqual('block');
|
|
expect(hotInstance.getDataAtCell(0, 0)).toEqual('A1');
|
|
|
|
await act(async () => {
|
|
simulateMouseEvent(document.querySelector('#editorComponentContainer button'), 'click');
|
|
});
|
|
|
|
expect(hotInstance.getDataAtCell(0, 0)).toEqual('new-value');
|
|
|
|
await act(async () => {
|
|
hotInstance.getActiveEditor().close();
|
|
});
|
|
|
|
expect((document.querySelector('#editorComponentContainer') as any).style.display).toEqual('none');
|
|
});
|
|
|
|
it('should use the correct editor inside HotTable component depends on its mount state', async () => {
|
|
let hotTableInstanceRef = React.createRef();
|
|
|
|
class WrapperComponent extends React.Component<any, any> {
|
|
state = {
|
|
editor: false,
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<HotTable licenseKey="non-commercial-and-evaluation"
|
|
id="test-hot"
|
|
data={createSpreadsheetData(3, 3)}
|
|
width={300}
|
|
height={300}
|
|
rowHeights={23}
|
|
colWidths={50}
|
|
init={function () {
|
|
mockElementDimensions(this.rootElement, 300, 300);
|
|
}}
|
|
ref={hotTableInstanceRef}>
|
|
{this.state.editor ? <EditorComponent hot-editor></EditorComponent> : null}
|
|
</HotTable>
|
|
);
|
|
};
|
|
}
|
|
|
|
const wrapperComponentInstance = mountComponentWithRef((
|
|
<WrapperComponent/>
|
|
));
|
|
|
|
let hotInstance = (hotTableInstanceRef.current as any).hotInstance;
|
|
|
|
await act(async() => {
|
|
hotInstance.selectCell(0, 0);
|
|
});
|
|
|
|
{
|
|
const activeEditor = hotInstance.getActiveEditor();
|
|
|
|
expect(activeEditor.constructor.name).toBe('TextEditor');
|
|
|
|
activeEditor.close();
|
|
}
|
|
|
|
await act(async() => {
|
|
wrapperComponentInstance.setState({ editor: true });
|
|
});
|
|
|
|
await sleep(100);
|
|
|
|
await act(async() => {
|
|
hotInstance.selectCell(0, 0);
|
|
});
|
|
|
|
{
|
|
const activeEditor = hotInstance.getActiveEditor();
|
|
|
|
expect(activeEditor.constructor.name).toBe('CustomEditor');
|
|
expect(activeEditor.editorComponent.__proto__.constructor.name).toBe('EditorComponent');
|
|
|
|
activeEditor.close();
|
|
}
|
|
|
|
await act(async() => {
|
|
wrapperComponentInstance.setState({ editor: false });
|
|
});
|
|
|
|
await sleep(100);
|
|
|
|
await act(async() => {
|
|
hotInstance.selectCell(0, 0);
|
|
});
|
|
|
|
{
|
|
const activeEditor = hotInstance.getActiveEditor();
|
|
|
|
expect(activeEditor.constructor.name).toBe('TextEditor');
|
|
|
|
activeEditor.close();
|
|
}
|
|
});
|
|
|
|
it('should use the correct renderer inside HotTable component depends on its mount state', async () => {
|
|
let hotTableInstanceRef = React.createRef();
|
|
|
|
class WrapperComponent extends React.Component<any, any> {
|
|
state = {
|
|
renderer: false,
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<HotTable licenseKey="non-commercial-and-evaluation"
|
|
id="test-hot"
|
|
data={createSpreadsheetData(3, 3)}
|
|
width={300}
|
|
height={300}
|
|
rowHeights={23}
|
|
colWidths={50}
|
|
init={function () {
|
|
mockElementDimensions(this.rootElement, 300, 300);
|
|
}}
|
|
ref={hotTableInstanceRef}>
|
|
{this.state.renderer ? <RendererComponent hot-renderer /> : null}
|
|
</HotTable>
|
|
);
|
|
};
|
|
}
|
|
|
|
const wrapperComponentInstance = mountComponentWithRef((
|
|
<WrapperComponent/>
|
|
));
|
|
|
|
let hotInstance = (hotTableInstanceRef.current as any).hotInstance;
|
|
|
|
await act(async() => {
|
|
hotInstance.selectCell(0, 0);
|
|
});
|
|
|
|
{
|
|
const activeRenderer = hotInstance.getCellRenderer(0, 0);
|
|
|
|
expect(activeRenderer.name).toBe('textRenderer');
|
|
}
|
|
|
|
await act(async() => {
|
|
wrapperComponentInstance.setState({ renderer: true });
|
|
});
|
|
|
|
await sleep(100);
|
|
|
|
await act(async() => {
|
|
hotInstance.selectCell(0, 0);
|
|
});
|
|
|
|
{
|
|
const activeRenderer = hotInstance.getCellRenderer(0, 0);
|
|
|
|
expect(activeRenderer.name).toBe('__internalRenderer');
|
|
}
|
|
|
|
await act(async() => {
|
|
wrapperComponentInstance.setState({ renderer: false });
|
|
});
|
|
|
|
await sleep(100);
|
|
|
|
await act(async() => {
|
|
hotInstance.selectCell(0, 0);
|
|
});
|
|
|
|
{
|
|
const activeRenderer = hotInstance.getCellRenderer(0, 0);
|
|
|
|
expect(activeRenderer.name).toBe('textRenderer');
|
|
}
|
|
});
|
|
});
|
|
|