Adds right, left, bottom, and bottom-left-corner resize handles alongside the existing bottom-right corner, and raises resize handle z-index above Handsontable's frozen header clones so clicks reach the handle instead of the table underneath it.
990 lines
31 KiB
TypeScript
990 lines
31 KiB
TypeScript
import { cloneDeep } from 'lodash-es'
|
|
|
|
const username = Cypress.env('username')
|
|
const password = Cypress.env('password')
|
|
const hostUrl = Cypress.env('hosturl')
|
|
const appLocation = Cypress.env('appLocation')
|
|
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
|
|
const serverType = Cypress.env('serverType')
|
|
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
|
|
|
|
context('editor tests: ', function () {
|
|
this.beforeAll(() => {
|
|
cy.visit(`${hostUrl}/SASLogon/logout`)
|
|
cy.loginAndUpdateValidKey(true)
|
|
})
|
|
|
|
this.beforeEach(() => {
|
|
cy.visit(hostUrl + appLocation)
|
|
cy.wait(2000)
|
|
|
|
cy.get('body').then(($body) => {
|
|
const usernameInput = $body.find('input.username')[0]
|
|
|
|
if (usernameInput && !Cypress.dom.isHidden(usernameInput)) {
|
|
cy.get('input.username').type(username)
|
|
cy.get('input.password').type(password)
|
|
|
|
cy.get('.login-group button').click()
|
|
}
|
|
})
|
|
|
|
visitPage('home')
|
|
})
|
|
|
|
it('1 | Add one viewbox', (done) => {
|
|
const viewbox_table = 'mpe_audit'
|
|
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
|
|
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
cy.get('.viewbox-open').click()
|
|
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
|
|
|
|
cy.contains('.open-viewbox', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
|
|
checkColumns(columns, () => {
|
|
done()
|
|
})
|
|
})
|
|
|
|
it('2 | Add two viewboxes', (done) => {
|
|
const viewboxes = [
|
|
{
|
|
viewbox_table: 'mpe_audit',
|
|
columns: ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
|
|
},
|
|
{
|
|
viewbox_table: 'mpe_alerts',
|
|
columns: [
|
|
'TX_FROM',
|
|
'ALERT_EVENT',
|
|
'ALERT_LIB',
|
|
'ALERT_DS',
|
|
'ALERT_USER'
|
|
]
|
|
}
|
|
]
|
|
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
cy.get('.viewbox-open').click()
|
|
openTableFromViewboxTree(
|
|
libraryToOpenIncludes,
|
|
viewboxes.map((viewbox) => viewbox.viewbox_table)
|
|
)
|
|
viewboxes.forEach((viewbox) => {
|
|
cy.contains('.open-viewbox', viewbox.viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
})
|
|
|
|
let result = 0
|
|
|
|
viewboxes.forEach((viewbox) => {
|
|
checkColumns(viewbox.columns, () => {
|
|
result++
|
|
|
|
if (result === viewboxes.length) done()
|
|
})
|
|
})
|
|
})
|
|
|
|
it('3 | Add viewbox, add columns', (done) => {
|
|
const viewbox_table = 'mpe_audit'
|
|
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
|
|
const additionalColumns = ['IS_PK']
|
|
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
cy.get('.viewbox-open').click()
|
|
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
|
|
|
|
cy.contains('.open-viewbox', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
|
|
openViewboxConfig(viewbox_table)
|
|
|
|
removeAllColumns()
|
|
|
|
addColumns(additionalColumns)
|
|
|
|
checkColumns([...columns, ...additionalColumns], () => {
|
|
done()
|
|
})
|
|
})
|
|
|
|
it('4 | Add viewbox, add columns and reorder', (done) => {
|
|
const viewbox_table = 'mpe_audit'
|
|
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
|
|
const additionalColumns = ['IS_PK', 'MOVE_TYPE']
|
|
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
cy.get('.viewbox-open').click()
|
|
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
|
|
|
|
cy.contains('.open-viewbox', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
|
|
openViewboxConfig(viewbox_table)
|
|
|
|
removeAllColumns()
|
|
|
|
addColumns(additionalColumns, () => {
|
|
cy.wait(1000)
|
|
//reorder
|
|
cy.get('.col-box.column-MOVE_TYPE')
|
|
.realMouseDown({ button: 'left', position: 'center' })
|
|
.realMouseMove(0, 10, { position: 'center' })
|
|
cy.wait(200) // In our case, we wait 200ms cause we have animations which we are sure that take this amount of time
|
|
cy.get('.col-box.column-IS_PK')
|
|
.realMouseMove(0, 0, { position: 'center' })
|
|
.realMouseUp()
|
|
//reorder end
|
|
|
|
cy.wait(500)
|
|
|
|
checkColumns([...columns, ...additionalColumns.reverse()], () => {
|
|
done()
|
|
})
|
|
})
|
|
})
|
|
|
|
it('5 | Add viewbox, add columns, reorder, remove column, add again', (done) => {
|
|
const viewbox_table = 'mpe_audit'
|
|
const columns = ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM']
|
|
const additionalColumns = ['IS_PK', 'MOVE_TYPE']
|
|
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
cy.get('.viewbox-open').click()
|
|
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
|
|
|
|
cy.contains('.open-viewbox', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
|
|
openViewboxConfig(viewbox_table)
|
|
|
|
removeAllColumns()
|
|
|
|
addColumns(additionalColumns, () => {
|
|
cy.wait(1000)
|
|
//reorder
|
|
cy.get('.col-box.column-MOVE_TYPE')
|
|
.realMouseDown({ button: 'left', position: 'center' })
|
|
.realMouseMove(0, 10, { position: 'center' })
|
|
cy.wait(200) // In our case, we wait 200ms cause we have animations which we are sure that take this amount of time
|
|
cy.get('.col-box.column-IS_PK')
|
|
.realMouseMove(0, 0, { position: 'center' })
|
|
.realMouseUp()
|
|
//reorder end
|
|
|
|
cy.wait(500)
|
|
|
|
checkColumns([...columns, ...additionalColumns.reverse()], () => {
|
|
const colToRemove = 'MOVE_TYPE'
|
|
|
|
removeColumn(colToRemove)
|
|
checkColumns(
|
|
[
|
|
...columns,
|
|
...additionalColumns.filter((col) => col !== colToRemove)
|
|
],
|
|
() => {
|
|
addColumns([colToRemove], () => {
|
|
checkColumns([...columns, ...additionalColumns.reverse()], () => {
|
|
done()
|
|
})
|
|
})
|
|
}
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
it('6 | Add viewboxes, reload and check url restored configuration', (done) => {
|
|
const viewboxes = [
|
|
{
|
|
viewbox_table: 'mpe_audit',
|
|
columns: ['LOAD_REF', 'LIBREF', 'DSN', 'KEY_HASH', 'TGTVAR_NM'],
|
|
additionalColumns: ['IS_PK', 'MOVE_TYPE']
|
|
},
|
|
{
|
|
viewbox_table: 'mpe_alerts',
|
|
columns: [
|
|
'TX_FROM',
|
|
'ALERT_EVENT',
|
|
'ALERT_LIB',
|
|
'ALERT_DS',
|
|
'ALERT_USER'
|
|
],
|
|
additionalColumns: ['TX_TO']
|
|
}
|
|
]
|
|
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
cy.get('.viewbox-open').click()
|
|
openTableFromViewboxTree(libraryToOpenIncludes, [
|
|
viewboxes[0].viewbox_table,
|
|
viewboxes[1].viewbox_table
|
|
])
|
|
|
|
openViewboxConfig(viewboxes[0].viewbox_table)
|
|
|
|
cy.wait(500)
|
|
|
|
removeAllColumns()
|
|
|
|
addColumns(viewboxes[0].additionalColumns, () => {
|
|
cy.wait(1000)
|
|
|
|
if (viewboxes[0].viewbox_table === 'mpe_audit') {
|
|
cy.get('.col-box.column-MOVE_TYPE')
|
|
.realMouseDown({ button: 'left', position: 'center' })
|
|
.realMouseMove(0, 10, { position: 'center' })
|
|
cy.wait(200) // In our case, we wait 200ms cause we have animations which we are sure that take this amount of time
|
|
cy.get('.col-box.column-IS_PK')
|
|
.realMouseMove(0, 0, { position: 'center' })
|
|
.realMouseUp()
|
|
}
|
|
|
|
cy.wait(1000)
|
|
|
|
openViewboxConfig(viewboxes[1].viewbox_table)
|
|
|
|
addColumns(viewboxes[1].additionalColumns, () => {
|
|
cy.wait(1000).reload()
|
|
|
|
let result = 0
|
|
|
|
checkColumns(
|
|
[
|
|
...viewboxes[0].columns,
|
|
...cloneDeep(viewboxes[0].additionalColumns.reverse())
|
|
],
|
|
() => {
|
|
result++
|
|
|
|
if (result === 2) done()
|
|
}
|
|
)
|
|
checkColumns(
|
|
[...viewboxes[1].columns, ...viewboxes[1].additionalColumns],
|
|
() => {
|
|
result++
|
|
|
|
if (result === 2) done()
|
|
}
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
// The base VIEW table and a viewbox on a different table must never
|
|
// share a filter dialog's cached state - each keeps its own entry in
|
|
// the globals filter cache (getFilterObjPath), keyed by viewboxId where
|
|
// one is present. Opened from the VIEW page specifically
|
|
// (rootParam === 'view') - the one branch where this previously broke,
|
|
// since a 'view'-page QueryComponent was always treated as the base
|
|
// table regardless of viewboxId.
|
|
it('7 | Base table filter and viewbox filter stay isolated from each other', () => {
|
|
const viewbox_table = 'mpe_audit'
|
|
|
|
visitPage('view/data')
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
// Filter the base table.
|
|
openViewFilter()
|
|
setFilterWithValue('SOME_CHAR', 'this is dummy data', 'value', () => {
|
|
cy.get('app-query', { withinSubject: null }).should('not.exist')
|
|
|
|
// Open a viewbox on a *different* table and check its filter dialog
|
|
// does not show the base table's filter.
|
|
openViewViewboxes()
|
|
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
|
|
closeViewboxModal()
|
|
|
|
// cy.contains retries until the viewbox's title actually shows the
|
|
// expected table name (it can render before its data/title loads),
|
|
// then .parents() walks back up to its container - avoids a
|
|
// one-shot .then() racing the title's own async update, which is
|
|
// what made this flaky in the first place.
|
|
cy.contains('.viewboxes-container .viewbox .table-title', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
.parents('.viewbox')
|
|
.first()
|
|
.within(() => {
|
|
cy.get('clr-icon[shape="filter"]').click()
|
|
|
|
cy.get('.filter-modal code.language-sql', {
|
|
withinSubject: null
|
|
}).should(($code) => {
|
|
expect($code.text()).to.not.contain('SOME_CHAR')
|
|
})
|
|
|
|
// Reverse direction: filter the viewbox, then confirm
|
|
// re-opening the base table's own filter dialog still shows
|
|
// the base table's filter, not the viewbox's.
|
|
setFilterWithValue('LIBREF', 'sasdemo', 'value', () => {
|
|
cy.get('app-query', { withinSubject: null }).should('not.exist')
|
|
|
|
openViewFilter()
|
|
|
|
cy.get('.filter-modal code.language-sql', {
|
|
withinSubject: null
|
|
}).should(($code) => {
|
|
expect($code.text()).to.contain('SOME_CHAR')
|
|
expect($code.text()).to.not.contain('LIBREF')
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
it('8 | Dragging a viewbox by its corner handle resizes it', () => {
|
|
const viewbox_table = 'mpe_audit'
|
|
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
cy.get('.viewbox-open').click()
|
|
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
|
|
|
|
cy.contains('.open-viewbox', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
|
|
// The Viewboxes picker modal is still open at this point, its backdrop
|
|
// sitting above .viewboxes-container - a real click (unlike cy.find(),
|
|
// which doesn't care what's visually on top) would land on the modal
|
|
// instead of the handle underneath it, silently no-opping the drag.
|
|
closeViewboxModal()
|
|
|
|
// Re-query by the title text rather than caching a reference, since
|
|
// the viewbox can still be re-rendering its content right after
|
|
// opening.
|
|
cy.contains('.viewboxes-container .viewbox .table-title', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
.parents('.viewbox')
|
|
.first()
|
|
.as('viewboxEl')
|
|
|
|
// tableOnClick() calls snapToGrid() asynchronously right after its data
|
|
// load resolves, which overwrites width/height/x/y for every open
|
|
// viewbox via the [style.width.px]/[style.height.px] bindings - totally
|
|
// independent of any drag. The table title above renders immediately
|
|
// (before that data load finishes), so capturing initialRect right
|
|
// after it appears races snapToGrid: the box can still silently resize
|
|
// itself out from under the test between capturing initialRect and the
|
|
// drag. Waiting for the table's own header cells (populated only once
|
|
// that same data load - and so snapToGrid - has completed) closes that
|
|
// race, same signal checkColumns() uses elsewhere in this file.
|
|
cy.get('@viewboxEl').find('.ht_master.handsontable thead tr th', {
|
|
timeout: longerCommandTimeout
|
|
})
|
|
|
|
cy.get('@viewboxEl').then(($box) => {
|
|
const initialRect = $box[0].getBoundingClientRect()
|
|
|
|
// Chained off one query, not re-queried between each step, so
|
|
// cypress-real-events keeps operating on the same subject throughout.
|
|
//
|
|
// Dragging INWARD (negative offsets, shrinking) rather than outward:
|
|
// a freshly opened viewbox starts flush against the right edge of the
|
|
// viewport (x: window.innerWidth - defaultConfig.width, so
|
|
// box.right === innerWidth exactly). realMouseMove's target
|
|
// coordinate is computed as an absolute point on the page, so dragging
|
|
// the corner outward/rightward to grow the box would target a point
|
|
// past the viewport's right edge, outside the Cypress AUT iframe
|
|
// entirely - Chrome never delivers a move event for a point outside
|
|
// the iframe. Shrinking stays well within the box's own footprint
|
|
// (500x300 default, 200x200 min per .viewbox's CSS), so the target
|
|
// coordinate is always safely on-screen - same resize() code path,
|
|
// just exercised in the direction that's actually simulatable.
|
|
cy.get('@viewboxEl')
|
|
.find('.dragHandle.corner')
|
|
.realMouseDown({ button: 'left', position: 'center' })
|
|
.realMouseMove(-100, -80, { position: 'topLeft' })
|
|
.realMouseUp()
|
|
|
|
// A generous threshold, not an exact pixel match - real rendering has
|
|
// its own small offsets (see setHandleTransform's "fine tune" +5).
|
|
// The point is confirming the box actually resizes at all in response
|
|
// to the drag.
|
|
cy.get('@viewboxEl').should(($box) => {
|
|
const rect = $box[0].getBoundingClientRect()
|
|
|
|
expect(rect.width, 'width after resize').to.be.lessThan(
|
|
initialRect.width - 30
|
|
)
|
|
expect(rect.height, 'height after resize').to.be.lessThan(
|
|
initialRect.height - 30
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
it('9 | Dragging a viewbox by its right-edge handle resizes width only', () => {
|
|
const viewbox_table = 'mpe_audit'
|
|
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
cy.get('.viewbox-open').click()
|
|
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
|
|
|
|
cy.contains('.open-viewbox', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
|
|
closeViewboxModal()
|
|
|
|
cy.contains('.viewboxes-container .viewbox .table-title', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
.parents('.viewbox')
|
|
.first()
|
|
.as('viewboxEl')
|
|
|
|
// See test 8's comment: snapToGrid() resizes every open viewbox
|
|
// asynchronously right after its data load resolves, racing initialRect
|
|
// capture if that happens right after the title (which renders before
|
|
// the load finishes) appears. Wait for the table's own header cells
|
|
// first, since those only populate once that same load has completed.
|
|
cy.get('@viewboxEl').find('.ht_master.handsontable thead tr th', {
|
|
timeout: longerCommandTimeout
|
|
})
|
|
|
|
cy.get('@viewboxEl').then(($box) => {
|
|
const initialRect = $box[0].getBoundingClientRect()
|
|
|
|
// Chained off one query - see test 8's comment on why re-querying
|
|
// between down/move/up silently drops the move. Dragging INWARD
|
|
// (negative offset, shrinking) rather than outward - see test 8's
|
|
// comment: a freshly opened viewbox starts flush against the
|
|
// viewport's right edge, so growing rightward would target a point
|
|
// past the AUT iframe's edge, which Chrome never delivers a move
|
|
// event for.
|
|
cy.get('@viewboxEl')
|
|
.find('.dragHandle.right')
|
|
.realMouseDown({ button: 'left', position: 'center' })
|
|
.realMouseMove(-100, 0, { position: 'topLeft' })
|
|
.realMouseUp()
|
|
|
|
cy.get('@viewboxEl').should(($box) => {
|
|
const rect = $box[0].getBoundingClientRect()
|
|
|
|
expect(rect.width, 'width after resize').to.be.lessThan(
|
|
initialRect.width - 30
|
|
)
|
|
// This handle must only affect width - height (and the box's own
|
|
// left edge) stay put.
|
|
expect(rect.height, 'height unchanged').to.be.closeTo(
|
|
initialRect.height,
|
|
5
|
|
)
|
|
expect(rect.left, 'left edge unchanged').to.be.closeTo(
|
|
initialRect.left,
|
|
5
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
it('10 | Dragging a viewbox by its bottom-left corner handle resizes it and moves its left edge', () => {
|
|
const viewbox_table = 'mpe_audit'
|
|
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
cy.get('.viewbox-open').click()
|
|
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
|
|
|
|
cy.contains('.open-viewbox', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
|
|
closeViewboxModal()
|
|
|
|
cy.contains('.viewboxes-container .viewbox .table-title', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
.parents('.viewbox')
|
|
.first()
|
|
.as('viewboxEl')
|
|
|
|
// See test 8's comment: wait for snapToGrid()'s async resize to settle
|
|
// before capturing initialRect, or the box can silently change size out
|
|
// from under the test.
|
|
cy.get('@viewboxEl').find('.ht_master.handsontable thead tr th', {
|
|
timeout: longerCommandTimeout
|
|
})
|
|
|
|
cy.get('@viewboxEl').then(($box) => {
|
|
const initialRect = $box[0].getBoundingClientRect()
|
|
|
|
// Chained off one query - see test 8's comment on why re-querying
|
|
// between down/move/up silently drops the move. Dragging this
|
|
// handle right+down (only positive offsets are meaningful for
|
|
// realMouseMove's position-anchored coordinates) shrinks width
|
|
// while moving the box's own left edge right by the same amount,
|
|
// and grows height - unlike every other handle here, this one is
|
|
// expected to move the box (viewbox.x) as well as resize it, which
|
|
// is the more error-prone half of this handler to cover.
|
|
cy.get('@viewboxEl')
|
|
.find('.dragHandle.corner-left')
|
|
.realMouseDown({ button: 'left', position: 'center' })
|
|
.realMouseMove(60, 80, { position: 'topLeft' })
|
|
.realMouseUp()
|
|
|
|
cy.get('@viewboxEl').should(($box) => {
|
|
const rect = $box[0].getBoundingClientRect()
|
|
|
|
expect(rect.width, 'width after resize').to.be.lessThan(
|
|
initialRect.width - 30
|
|
)
|
|
expect(rect.left, 'left edge moved right').to.be.greaterThan(
|
|
initialRect.left + 30
|
|
)
|
|
expect(rect.height, 'height after resize').to.be.greaterThan(
|
|
initialRect.height + 30
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
it('11 | Dragging a viewbox by its left-edge handle resizes width and moves its left edge, leaving height alone', () => {
|
|
const viewbox_table = 'mpe_audit'
|
|
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
cy.get('.viewbox-open').click()
|
|
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
|
|
|
|
cy.contains('.open-viewbox', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
|
|
closeViewboxModal()
|
|
|
|
cy.contains('.viewboxes-container .viewbox .table-title', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
.parents('.viewbox')
|
|
.first()
|
|
.as('viewboxEl')
|
|
|
|
// See test 8's comment: wait for snapToGrid()'s async resize to settle
|
|
// before capturing initialRect, or the box can silently change size out
|
|
// from under the test.
|
|
cy.get('@viewboxEl').find('.ht_master.handsontable thead tr th', {
|
|
timeout: longerCommandTimeout
|
|
})
|
|
|
|
cy.get('@viewboxEl').then(($box) => {
|
|
const initialRect = $box[0].getBoundingClientRect()
|
|
|
|
// Chained off one query - see test 8's comment on why re-querying
|
|
// between down/move/up silently drops the move. Dragging this
|
|
// handle right (only positive offsets are meaningful for
|
|
// realMouseMove's position-anchored coordinates) shrinks width
|
|
// while moving the box's own left edge right by the same amount -
|
|
// same viewbox.x-moving behavior as the bottom-left corner, minus
|
|
// the height change.
|
|
cy.get('@viewboxEl')
|
|
.find('.dragHandle.left')
|
|
.realMouseDown({ button: 'left', position: 'center' })
|
|
.realMouseMove(60, 0, { position: 'topLeft' })
|
|
.realMouseUp()
|
|
|
|
cy.get('@viewboxEl').should(($box) => {
|
|
const rect = $box[0].getBoundingClientRect()
|
|
|
|
expect(rect.width, 'width after resize').to.be.lessThan(
|
|
initialRect.width - 30
|
|
)
|
|
expect(rect.left, 'left edge moved right').to.be.greaterThan(
|
|
initialRect.left + 30
|
|
)
|
|
expect(rect.height, 'height unchanged').to.be.closeTo(
|
|
initialRect.height,
|
|
5
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
it('12 | Dragging a viewbox by its bottom-edge handle resizes height only', () => {
|
|
const viewbox_table = 'mpe_audit'
|
|
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
cy.get('.viewbox-open').click()
|
|
openTableFromViewboxTree(libraryToOpenIncludes, [viewbox_table])
|
|
|
|
cy.contains('.open-viewbox', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
|
|
closeViewboxModal()
|
|
|
|
cy.contains('.viewboxes-container .viewbox .table-title', viewbox_table, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
})
|
|
.parents('.viewbox')
|
|
.first()
|
|
.as('viewboxEl')
|
|
|
|
// See test 8's comment: wait for snapToGrid()'s async resize to settle
|
|
// before capturing initialRect, or the box can silently change size out
|
|
// from under the test.
|
|
cy.get('@viewboxEl').find('.ht_master.handsontable thead tr th', {
|
|
timeout: longerCommandTimeout
|
|
})
|
|
|
|
cy.get('@viewboxEl').then(($box) => {
|
|
const initialRect = $box[0].getBoundingClientRect()
|
|
|
|
// Chained off one query - see test 8's comment on why re-querying
|
|
// between down/move/up silently drops the move.
|
|
cy.get('@viewboxEl')
|
|
.find('.dragHandle.bottom')
|
|
.realMouseDown({ button: 'left', position: 'center' })
|
|
.realMouseMove(0, 100, { position: 'topLeft' })
|
|
.realMouseUp()
|
|
|
|
cy.get('@viewboxEl').should(($box) => {
|
|
const rect = $box[0].getBoundingClientRect()
|
|
|
|
expect(rect.height, 'height after resize').to.be.greaterThan(
|
|
initialRect.height + 30
|
|
)
|
|
// This handle must only affect height - width (and the box's own
|
|
// left edge) stay put.
|
|
expect(rect.width, 'width unchanged').to.be.closeTo(
|
|
initialRect.width,
|
|
5
|
|
)
|
|
expect(rect.left, 'left edge unchanged').to.be.closeTo(
|
|
initialRect.left,
|
|
5
|
|
)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
const removeAllColumns = () => {
|
|
cy.get('.configuration-wrapper clr-icon[shape="trash"]').then(
|
|
(removeNodes) => {
|
|
for (let removeNode of removeNodes) {
|
|
removeNode.click()
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
// Asserts that at least one open viewbox currently shows exactly this
|
|
// column list, in order. Uses a single .should() callback (synchronous
|
|
// jQuery reads only, no nested cy.get()/.then()) so Cypress retries the
|
|
// whole check against a fresh DOM snapshot until it passes or times out -
|
|
// unlike .then(), which only ever sees one snapshot and, combined with an
|
|
// early return on a mismatch, would silently never invoke the caller's
|
|
// callback while Handsontable/the viewbox's data are still loading or
|
|
// re-rendering asynchronously.
|
|
const checkColumns = (columns: string[], callback: () => void) => {
|
|
cy.get('.viewboxes-container .viewbox', { withinSubject: null }).should(
|
|
($viewboxNodes) => {
|
|
const matches = $viewboxNodes.toArray().some((viewboxNode) => {
|
|
// rowHeaders: true (viewboxes.component.ts) adds a leading, unlabeled
|
|
// corner <th> before the actual data columns - skip it, or every
|
|
// comparison is off by one against the row-header cell instead of
|
|
// the first real column.
|
|
const headerCells = Cypress.$(viewboxNode)
|
|
.find('.ht_master.handsontable thead tr th')
|
|
.toArray()
|
|
.slice(1)
|
|
|
|
return columns.every((col, i) =>
|
|
(headerCells[i]?.innerHTML || '')
|
|
.toLowerCase()
|
|
.includes(col.toLowerCase())
|
|
)
|
|
})
|
|
|
|
expect(matches, `a viewbox showing columns: ${columns.join(', ')}`).to.be
|
|
.true
|
|
}
|
|
)
|
|
|
|
cy.then(() => callback())
|
|
}
|
|
|
|
const closeViewboxModal = () => {
|
|
cy.get('app-viewboxes .close', { withinSubject: null }).click()
|
|
}
|
|
|
|
const removeColumn = (column: string) => {
|
|
cy.get(`.col-box.column-${column} clr-icon`, { withinSubject: null }).click()
|
|
}
|
|
|
|
const addColumns = (columns: string[], callback?: () => void) => {
|
|
for (const column of columns) {
|
|
cy.get('.cols-search input', { withinSubject: null }).type(column)
|
|
cy.get('.cols-search .autocomplete-wrapper', { withinSubject: null })
|
|
.first()
|
|
.trigger('keydown', { key: 'ArrowDown' })
|
|
cy.get('.cols-search .autocomplete-wrapper', { withinSubject: null })
|
|
.first()
|
|
.trigger('keydown', { key: 'Enter' })
|
|
|
|
// Wait for the actual column box to render before moving on to the
|
|
// next column (or firing the callback) - selecting it doesn't add it
|
|
// to the DOM synchronously, and cy.get()'s own retry covers that gap
|
|
// instead of relying on a fixed cy.wait().
|
|
cy.get(`.col-box.column-${column}`, { timeout: longerCommandTimeout })
|
|
}
|
|
|
|
if (callback) cy.then(() => callback())
|
|
}
|
|
|
|
// The VIEW page's "options" dropdown (viewer.component.html) - distinct
|
|
// from the editor page's dedicated .viewbox-open button used by the
|
|
// tests above, since test 7 runs on the VIEW page specifically.
|
|
const openViewOptionsDropdown = () => {
|
|
cy.get('.filterSide', { withinSubject: null }).click()
|
|
}
|
|
|
|
const openViewFilter = () => {
|
|
openViewOptionsDropdown()
|
|
cy.get('[clrDropdownItem]', { withinSubject: null })
|
|
.contains('Filter')
|
|
.click()
|
|
}
|
|
|
|
const openViewViewboxes = () => {
|
|
openViewOptionsDropdown()
|
|
cy.get('[clrDropdownItem]', { withinSubject: null })
|
|
.contains('Viewboxes')
|
|
.click()
|
|
}
|
|
|
|
// cy.contains retries until a matching node appears/updates - the open
|
|
// viewbox's own list entry can render before its table-name text has
|
|
// actually loaded, so a one-shot .then() lookup here would race it.
|
|
const openViewboxConfig = (viewbox_tablename: string) => {
|
|
cy.contains('.open-viewbox', viewbox_tablename, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
}).click()
|
|
}
|
|
|
|
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
|
|
cy.get('.app-loading', { timeout: longerCommandTimeout }).should('not.exist')
|
|
|
|
// Small settle wait: right after a fresh visit (an extra navigation on
|
|
// top of beforeEach's own, e.g. test 7's visitPage('view/data')), the
|
|
// tree component can still be re-rendering, causing a node to be found
|
|
// then swapped out mid-click - same fix already proven in
|
|
// viewer-labels.cy.ts.
|
|
cy.wait(300)
|
|
|
|
const libraryNodeSelector = '.nav-tree clr-tree > clr-tree-node'
|
|
const libraryMatcher = new RegExp(libNameIncludes, 'i')
|
|
|
|
cy.contains(libraryNodeSelector, libraryMatcher, {
|
|
timeout: longerCommandTimeout
|
|
})
|
|
.find('.clr-tree-node-content-container p')
|
|
.click()
|
|
|
|
// Re-query the library node fresh rather than reusing the reference
|
|
// from before the click above - expanding a library can replace its
|
|
// whole DOM subtree once its child tables load (that's what made the
|
|
// earlier cached-reference version of this helper flaky: Cypress
|
|
// detected the click's own side effect detaching the element it was
|
|
// still verifying actionability against).
|
|
cy.contains(libraryNodeSelector, libraryMatcher, {
|
|
timeout: longerCommandTimeout
|
|
}).within(() => {
|
|
cy.contains('.clr-treenode-link', tablename, {
|
|
matchCase: false,
|
|
timeout: longerCommandTimeout
|
|
}).click()
|
|
})
|
|
}
|
|
|
|
const setFilterWithValue = (
|
|
variableValue: string,
|
|
valueString: string,
|
|
valueField: 'value' | 'time' | 'date' | 'datetime' | 'in' | 'between',
|
|
callback?: any
|
|
) => {
|
|
cy.wait(600)
|
|
|
|
cy.focused().type(variableValue)
|
|
cy.wait(100)
|
|
// cy.focused().trigger('input')
|
|
cy.get('.variable-col .autocomplete-wrapper', { withinSubject: null })
|
|
.first()
|
|
.trigger('keydown', { key: 'ArrowDown' })
|
|
cy.get('.variable-col .autocomplete-wrapper', {
|
|
withinSubject: null
|
|
}).trigger('keydown', { key: 'Enter' })
|
|
cy.focused().tab()
|
|
cy.wait(100)
|
|
|
|
if (valueField === 'in') {
|
|
cy.focused().select(valueField.toUpperCase()).trigger('change')
|
|
} else if (valueField === 'between') {
|
|
cy.focused().select(valueField.toUpperCase()).trigger('change')
|
|
} else {
|
|
cy.focused().tab()
|
|
cy.wait(100)
|
|
}
|
|
|
|
switch (valueField) {
|
|
case 'value': {
|
|
cy.focused().type(valueString)
|
|
|
|
break
|
|
}
|
|
case 'time': {
|
|
cy.focused().type(valueString)
|
|
|
|
break
|
|
}
|
|
case 'date': {
|
|
cy.focused().type(valueString)
|
|
cy.focused().tab()
|
|
|
|
break
|
|
}
|
|
case 'datetime': {
|
|
const date = valueString.split(' ')[0]
|
|
const time = valueString.split(' ')[1]
|
|
|
|
cy.focused().type(date)
|
|
cy.focused().tab()
|
|
cy.focused().tab()
|
|
cy.focused().type(time)
|
|
|
|
break
|
|
}
|
|
case 'in': {
|
|
cy.get('.checkbox-vals').then(() => {
|
|
cy.focused().tab()
|
|
cy.focused().click()
|
|
cy.get('.no-values')
|
|
.should('not.exist')
|
|
.then(() => {
|
|
cy.get('clr-checkbox-wrapper input').then((inputs: any) => {
|
|
inputs[0].click()
|
|
cy.get('.in-values-modal .modal-footer button').click()
|
|
|
|
cy.get('.modal-footer .btn-success-outline').click()
|
|
|
|
if (callback) callback()
|
|
})
|
|
})
|
|
})
|
|
|
|
break
|
|
}
|
|
case 'between': {
|
|
cy.focused().tab()
|
|
|
|
const start = valueString.split('-')[0]
|
|
const end = valueString.split('-')[1]
|
|
|
|
cy.focused().type(start)
|
|
cy.focused().tab()
|
|
cy.focused().type(end)
|
|
}
|
|
default: {
|
|
break
|
|
}
|
|
}
|
|
|
|
cy.wait(100)
|
|
cy.focused().tab()
|
|
cy.wait(100)
|
|
cy.focused().tab()
|
|
cy.wait(100)
|
|
cy.focused().tab()
|
|
cy.wait(100)
|
|
cy.focused().tab()
|
|
cy.wait(100)
|
|
cy.focused().click()
|
|
|
|
if (callback) callback()
|
|
}
|
|
|
|
const openTableFromViewboxTree = (
|
|
libNameIncludes: string,
|
|
tablenames: string[]
|
|
) => {
|
|
cy.get('.add-new clr-tree > clr-tree-node', {
|
|
timeout: longerCommandTimeout
|
|
}).then((treeNodes: any) => {
|
|
let viyaLib
|
|
|
|
for (let node of treeNodes) {
|
|
if (node.innerText.toLowerCase().includes(libNameIncludes)) {
|
|
viyaLib = node
|
|
break
|
|
}
|
|
}
|
|
|
|
cy.get(viyaLib).within(() => {
|
|
cy.get('p')
|
|
.click()
|
|
.then(() => {
|
|
cy.get('.clr-treenode-link').then(async (innerNodes: any) => {
|
|
for (let innerNode of innerNodes) {
|
|
for (let tablename of tablenames) {
|
|
await pause(300)
|
|
if (innerNode.innerText.toLowerCase().includes(tablename)) {
|
|
innerNode.click()
|
|
}
|
|
}
|
|
}
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
const pause = (ms: number) => {
|
|
return new Promise((resolve, reject) => {
|
|
setTimeout(() => {
|
|
resolve(null)
|
|
}, ms)
|
|
})
|
|
}
|
|
|
|
const visitPage = (url: string) => {
|
|
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
|
|
}
|