diff --git a/QualityControl/public/layout/Layout.js b/QualityControl/public/layout/Layout.js index 81c805581..e78d4b007 100644 --- a/QualityControl/public/layout/Layout.js +++ b/QualityControl/public/layout/Layout.js @@ -261,6 +261,8 @@ export default class Layout extends BaseViewModel { const result = await this.model.services.layout.saveLayout(this.item); if (result.isSuccess()) { await this.model.services.layout.getLayoutsByUserId(this.model.session.personid); + this._tabIndex = this._tabIndex < this.item.tabs.length ? this._tabIndex : 0; + this.selectTab(this._tabIndex); this.model.notification.show(`Layout "${this.item.name}" has been saved successfully.`, 'success'); } else { this.item = this.editOriginalClone; @@ -287,9 +289,7 @@ export default class Layout extends BaseViewModel { this.gridListSize = parseInt(value, 10); this.cellHeight = 100 / this.gridListSize * 0.95; // %, put some margin at bottom to see below this.cellWidth = 100 / this.gridListSize; // % - if (this.editEnabled) { - this.gridList.resizeGrid(this.gridListSize); - } + this.gridList.resizeGrid(this.gridListSize); this.tab.columns = this.gridListSize; this.tab.objects.forEach((object) => { if (object.w > this.tab.columns) { @@ -306,9 +306,7 @@ export default class Layout extends BaseViewModel { */ sortObjectsOfCurrentTab() { this.gridList.items = this.tab.objects; - if (this.editEnabled) { - this.gridList.resizeGrid(this.gridListSize); - } + this.gridList.resizeGrid(this.gridListSize); } /** diff --git a/QualityControl/test/public/pages/layout-show.test.js b/QualityControl/test/public/pages/layout-show.test.js index 33961c003..cc18f809a 100644 --- a/QualityControl/test/public/pages/layout-show.test.js +++ b/QualityControl/test/public/pages/layout-show.test.js @@ -13,7 +13,10 @@ import { strictEqual, ok, deepStrictEqual } from 'node:assert'; import { delay } from '../../testUtils/delay.js'; -import { editedMockedLayout } from '../../setup/seeders/layout-show/json-file-mock.js'; +import { + editedManyObjectsMockedLayout, + editedMockedLayout, +} from '../../setup/seeders/layout-show/json-file-mock.js'; import { getElementCenter } from '../../testUtils/dragAndDrop.js'; /** @@ -187,7 +190,7 @@ export const layoutShowTests = async (url, page, timeout = 5000, testParent) => const leftStyle = await page.evaluate(() => document.querySelector('#subcanvas .dropdown-menu').style.left); strictEqual(leftStyle, '0.1em'); - } + }, ); await testParent.test('should have second tab to be empty (according to demo data)', { timeout }, async () => { @@ -349,7 +352,7 @@ export const layoutShowTests = async (url, page, timeout = 5000, testParent) => elements.map((element) => element.textContent.trim())); strictEqual(tabNames[1], originalTabNames[0]); - } + }, ); await testParent.test( @@ -446,9 +449,69 @@ export const layoutShowTests = async (url, page, timeout = 5000, testParent) => }, ); + await testParent.test( + 'should display all objects even when the JSON has out of bound entries', + { timeout: 15000 }, + async () => { + const EXPECTED_LAYOUT_OBJECT_COUNT = editedManyObjectsMockedLayout.tabs[0].objects.length; + + /** + * Counts the number of visible child elements within #subcanvas. + * Visibility is defined as being within the viewport. + * @returns {number} The amount of visible rendered objects + */ + const getVisibleObjectCount = async () => + await page.evaluate(() => { + const container = document.querySelector('#subcanvas'); + if (!container) { + return 0; + } + + // Count elements that intersect with the viewport + return Array.from(container.children).filter((child) => { + const rect = child.getBoundingClientRect(); + return rect.bottom >= 0 && rect.top >= 0 && rect.left >= 0 && rect.right <= window.innerWidth; + }).length; + }); + + // Ensure we are on the main tab + await page.locator('#tab-0').click(); + await delay(100); + + const pencilButtonPath = '.btn-group > div > button'; + await page.locator(pencilButtonPath).click(); + + const editViaJSONButtonPath = '#editByJson'; + await page.locator(editViaJSONButtonPath).click(); + + const textareaPath = '#layout-json-editor'; + const mockedJSON = JSON.stringify(editedManyObjectsMockedLayout); + await page.locator(textareaPath).fill(mockedJSON); + + const updateButtonPath = '#updateLayoutButton'; + await page.locator(updateButtonPath).click(); + await delay(100); + + strictEqual( + await getVisibleObjectCount(), + EXPECTED_LAYOUT_OBJECT_COUNT, + `Expected ${EXPECTED_LAYOUT_OBJECT_COUNT} rendered objects after JSON update`, + ); + + await page.reload({ waitUntil: 'networkidle0' }); + await delay(100); + + strictEqual( + await getVisibleObjectCount(), + EXPECTED_LAYOUT_OBJECT_COUNT, + `Expected ${EXPECTED_LAYOUT_OBJECT_COUNT} rendered objects after page reload`, + ); + }, + ); + await testParent.test( 'should update layout when clicking "Update layout"', - { timeout }, + { timeout: 10000 }, async () => { const pencilButtonPath = '.btn-group > div > button'; await page.locator(pencilButtonPath).click(); @@ -474,16 +537,20 @@ export const layoutShowTests = async (url, page, timeout = 5000, testParent) => ); await testParent.test('should change tab after set tabInterval', { timeout: 15000 }, async () => { + // Ensure we are on the 'a' tab + await page.locator('#tab-1').click(); + await delay(100); + const location = await page.evaluate(() => window.location); strictEqual(location.search, `?page=layoutShow&layoutId=${LAYOUT_ID}&tab=a`); - await delay(11000); + await delay(11111); const location2 = await page.evaluate(() => window.location); strictEqual(location2.search, `?page=layoutShow&layoutId=${LAYOUT_ID}&tab=test`); }); await testParent.test( 'should update layout name in sidebar when name is changed and saved via JSON editor', - { timeout }, + { timeout: 10000 }, async () => { const originalSidebarName = await page.evaluate(() => { const sidebarLayoutLink = document.querySelector('nav a.menu-item.w-wrapped.selected span:nth-child(2)'); diff --git a/QualityControl/test/setup/seeders/layout-show/json-file-mock.js b/QualityControl/test/setup/seeders/layout-show/json-file-mock.js index ac7e7b420..197be352d 100644 --- a/QualityControl/test/setup/seeders/layout-show/json-file-mock.js +++ b/QualityControl/test/setup/seeders/layout-show/json-file-mock.js @@ -30,3 +30,58 @@ export const editedMockedLayout = { displayTimestamp: false, autoTabChange: 11, }; + +export const editedManyObjectsMockedLayout = { + name: 'a-test', + tabs: [ + { + name: 'main', + objects: [ + { + x: 0, + y: 0, + h: 1, + w: 1, + name: 'qc/test/object/1', + options: [], + ignoreDefaults: false, + }, + { + x: 1, + y: 0, + h: 1, + w: 1, + name: 'qc/test/object/1', + options: [], + ignoreDefaults: false, + }, + { + x: 2, + y: 0, + h: 1, + w: 1, + name: 'qc/test/object/1', + options: [], + ignoreDefaults: false, + }, + { + x: 0, + y: 1, + h: 1, + w: 1, + name: 'qc/test/object/1', + options: [], + ignoreDefaults: false, + }, + ], + columns: 1, + }, + { + name: 'a', + objects: [], + columns: 1, + }, + ], + displayTimestamp: false, + autoTabChange: 11, +};