diff --git a/src/model.ts b/src/model.ts index ce308ebf..81d73326 100644 --- a/src/model.ts +++ b/src/model.ts @@ -30,7 +30,6 @@ export class JupyterCadModel implements IJupyterCadModel { this.modelDB = modelDB || new ModelDB(); this.sharedModel.changed.connect(this._onSharedModelChanged); this.sharedModel.awareness.on('change', this._onClientStateChanged); - this.sharedModel; } get isDisposed(): boolean { diff --git a/src/panelview/objecttree.tsx b/src/panelview/objecttree.tsx index da477e79..6ff8473a 100644 --- a/src/panelview/objecttree.tsx +++ b/src/panelview/objecttree.tsx @@ -81,7 +81,7 @@ interface IStates { clientId: number | null; // ID of the yjs client id: string; // ID of the component, it is used to identify which component //is the source of awareness updates. - openNodes: (string | number)[]; + openNodes: (number | string)[]; } interface IProps { @@ -107,24 +107,24 @@ class ObjectTreeReact extends React.Component { this.props.cpModel.jcadModel?.sharedModelChanged.connect( this._sharedJcadModelChanged ); - this.props.cpModel.documentChanged.connect((_, changed) => { - if (changed) { + this.props.cpModel.documentChanged.connect((_, document) => { + if (document) { this.props.cpModel.disconnect(this._sharedJcadModelChanged); this.props.cpModel.disconnect(this._handleThemeChange); this.props.cpModel.disconnect(this._onClientSharedStateChanged); - changed.context.model.sharedModelChanged.connect( + document.context.model.sharedModelChanged.connect( this._sharedJcadModelChanged ); - changed.context.model.themeChanged.connect(this._handleThemeChange); - changed.context.model.clientStateChanged.connect( + document.context.model.themeChanged.connect(this._handleThemeChange); + document.context.model.clientStateChanged.connect( this._onClientSharedStateChanged ); this.setState(old => ({ ...old, - filePath: changed.context.localPath, + filePath: document.context.localPath, jcadObject: this.props.cpModel.jcadModel?.getAllObject(), - clientId: changed.context.model.getClientId() + clientId: document.context.model.getClientId() })); } else { this.setState({ @@ -183,13 +183,15 @@ class ObjectTreeReact extends React.Component { }; private _sharedJcadModelChanged = ( - _, - changed: IJupyterCadDocChange + sender: IJupyterCadModel, + change: IJupyterCadDocChange ): void => { - this.setState(old => ({ - ...old, - jcadObject: this.props.cpModel.jcadModel?.getAllObject() - })); + if (change.objectChange) { + this.setState(old => ({ + ...old, + jcadObject: this.props.cpModel.jcadModel?.getAllObject() + })); + } }; private _onClientSharedStateChanged = ( @@ -209,54 +211,66 @@ class ObjectTreeReact extends React.Component { localState.selected.emitter !== this.state.id ) { const selectedNode = localState.selected.value!; - this.setState(old => ({ - ...old, - selectedNode, - openNodes: [...old.openNodes, selectedNode] - })); + + const openNodes = [...this.state.openNodes]; + const index = openNodes.indexOf(selectedNode); + + if (index === -1) { + openNodes.push(selectedNode); + } + + this.setState(old => ({ ...old, selectedNode, openNodes })); } } } }; render(): React.ReactNode { + const { selectedNode, openNodes } = this.state; const data = this.stateToTree(); - let selectedNode: (number | string)[] = []; - if (this.state.selectedNode) { - const parentNode = data.filter( - node => node.id === this.state.selectedNode - ); + let selectedNodes: (number | string)[] = []; + if (selectedNode) { + const parentNode = data.filter(node => node.id === selectedNode); if (parentNode.length > 0 && parentNode[0].items.length > 0) { - selectedNode = [parentNode[0].items[0].id]; + selectedNodes = [parentNode[0].items[0].id]; } } + return (
{ if (id && id.length > 0) { let name = id[0] as string; + if (name.includes('#')) { name = name.split('#')[0]; - this.props.cpModel.jcadModel?.syncSelectedObject( name, this.state.id ); + return; + } + + const openNodes = [...this.state.openNodes]; + const index = openNodes.indexOf(name); + + if (index !== -1) { + openNodes.splice(index, 1); + } else { + openNodes.push(name); } + this.setState(old => ({ ...old, openNodes })); } else { this.props.cpModel.jcadModel?.syncSelectedObject(undefined); } }} - onToggleOpenNodes={nodes => - this.setState(old => ({ ...old, openNodes: nodes })) - } RenderNode={options => { // const paddingLeft = 25 * (options.level + 1); const jcadObj = this.getObjectFromName( diff --git a/ui-tests/package.json b/ui-tests/package.json index e8b71afb..9032ba0d 100644 --- a/ui-tests/package.json +++ b/ui-tests/package.json @@ -6,7 +6,8 @@ "scripts": { "start": "jupyter lab --config jupyter_server_test_config.py", "test": "jlpm playwright test", - "test:update": "jlpm playwright test --update-snapshots" + "test:update": "jlpm playwright test --update-snapshots", + "test:debug": "PWDEBUG=1 jlpm playwright test" }, "devDependencies": { "@jupyterlab/galata": "^5.0.0-alpha.14" diff --git a/ui-tests/tests/tree.spec.ts b/ui-tests/tests/tree.spec.ts new file mode 100644 index 00000000..022bc912 --- /dev/null +++ b/ui-tests/tests/tree.spec.ts @@ -0,0 +1,66 @@ +import { expect, test, galata } from '@jupyterlab/galata'; +import path from 'path'; + +test.use({ autoGoto: false }); + +test.describe('Tree UI test', () => { + let errors = 0; + test.beforeEach(async ({ page, request }) => { + page.setViewportSize({ width: 1920, height: 1080 }); + page.on('console', message => { + if (message.type() === 'error') { + errors += 1; + } + }); + + const content = galata.newContentsHelper(request); + await content.deleteDirectory('/examples'); + await content.uploadDirectory( + path.resolve(__dirname, '../../examples'), + '/examples' + ); + }); + + test.afterEach(async ({ page }) => { + errors = 0; + }); + + test(`Should display the object tree`, async ({ page }) => { + await page.goto(); + + const fileName = 'example1.FCStd'; + const fullPath = `examples/${fileName}`; + await page.notebook.openByPath(fullPath); + await page.notebook.activate(fullPath); + await page.locator('div.jpcad-Spinner').waitFor({ state: 'hidden' }); + + // Close the property panel + await page + .getByRole('tablist', { name: 'alternate sidebar' }) + .getByRole('tab', { name: 'JupyterCad Control Panel' }) + .click(); + + await page + .locator('[data-test-id="react-tree-root"] div.jpcad-control-panel-tree') + .nth(0) + .click(); + await page + .locator('[data-test-id="react-tree-root"] div.jpcad-control-panel-tree') + .nth(2) + .click(); + await page + .locator('[data-test-id="react-tree-root"] div.jpcad-control-panel-tree') + .nth(4) + .click(); + + expect(errors).toBe(0); + const tree = await page.getByRole('region', { + name: 'Objects tree Section' + }); + if (tree) { + expect(await tree.screenshot()).toMatchSnapshot({ + name: `Tree-Display-${fileName}.png` + }); + } + }); +}); diff --git a/ui-tests/tests/tree.spec.ts-snapshots/Tree-Display-example1-FCStd-linux.png b/ui-tests/tests/tree.spec.ts-snapshots/Tree-Display-example1-FCStd-linux.png new file mode 100644 index 00000000..84202a99 Binary files /dev/null and b/ui-tests/tests/tree.spec.ts-snapshots/Tree-Display-example1-FCStd-linux.png differ