The stage that reports what the file is doing is meant to belong to the whole file, and it finished at "Extracting the data finished" whatever happened. A sheet whose parse rejected had already blanked its datasource and dropped itself from the submission in the per-sheet catch, so the user was told the file had extracted while a table was silently missing from the submission. `allSettled` was already the right primitive, but its results said nothing: the catch swallowed the error, so every promise fulfilled. The catch now names the sheet on the component, the stage ends at a new `extractionFailed` state when anything was left out, and the pane lists what was left out and why. The two reasons a sheet contributes nothing are kept apart, because they are not the same thing to the reader: a sheet the sheet utility could not read is a "could not extract" problem in red, while a sheet it parsed and found no rows in - which arrives as the rejection "No relevant data found" - is reported as "no rows found" without calling the file corrupt. Both leave the sheet out of the submission, and either one stops the stage reporting a clean finish. The row derivation sits inside the try with the parse. Outside it, a throw on a row shape the sheet utility did not expect escaped as a rejection that `allSettled` swallows - the dataset stayed in the submission with `parsingTable` true and the stage reported a clean finish over a sheet that had been dropped. The messages live in the no-table-selected pane, which is where the user is once extraction has finished; the file pane they were first put in unmounts as soon as the datasets are fetched, so they would never have been read there. The stage line keeps one id per state rather than two of the same: `file-loading-stage` in the file pane while the datasets are fetched, `file-extraction-stage` in the overlay while the sheets are extracted, and the Cypress spec asks for both. The Cypress spec asks for both stage ids, because the element that carries the stage changes state, and asserts that a sheet left out of the submission is named in the pane - under whichever reason the sheet utility reported, since only the utility decides which of the two it is. The spec builds the component through the test injector rather than with seven positional arguments, so a constructor that gains or reorders a dependency fails there (`NG0201: No provider found for ElementRef`) instead of binding a stub to the wrong collaborator and passing.
368 lines
13 KiB
TypeScript
368 lines
13 KiB
TypeScript
import { Callbacks } from 'cypress/types/jquery/index'
|
|
|
|
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}`)
|
|
const fixturePath = 'excels_multi_load/'
|
|
|
|
const library = 'DC996664'
|
|
const mpeXTestTable = 'MPE_X_TEST'
|
|
const mpeTablesTable = 'MPE_TABLES'
|
|
|
|
context('excel multi load tests: ', function () {
|
|
this.beforeAll(() => {
|
|
cy.visit(`${hostUrl}/SASLogon/logout`)
|
|
cy.loginAndUpdateValidKey(true)
|
|
})
|
|
|
|
this.beforeEach(() => {
|
|
cy.visit(hostUrl + appLocation)
|
|
|
|
visitPage('home/multi-load')
|
|
|
|
colorLog(
|
|
`TEST START ---> ${
|
|
Cypress.mocha.getRunner().suite.ctx.currentTest.title
|
|
}`,
|
|
'#3498DB'
|
|
)
|
|
})
|
|
|
|
it('1 | Uploads Excel file with multiple sheets, 3 sheets including data, 2 sheets matched with dataset', (done) => {
|
|
attachExcelFile('multi_load_test_2.xlsx', () => {
|
|
checkHotUserDatasetTable(
|
|
'hotTableUserDataset',
|
|
[
|
|
[library, mpeXTestTable],
|
|
[library, mpeTablesTable]
|
|
],
|
|
() => {
|
|
cy.get('#continue-btn')
|
|
.trigger('click')
|
|
.then(() => {
|
|
checkIfTreeHasTables(
|
|
[`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`],
|
|
undefined,
|
|
(includes: boolean) => {
|
|
if (includes) {
|
|
// MPE_TABLES is left out of the submission, and the pane
|
|
// says why - as a sheet that could not be read or as one
|
|
// with no rows, depending on what the sheet utility
|
|
// reported. Either way it is named.
|
|
cy.get('#file-extraction-failures, #file-empty-sheets', {
|
|
timeout: longerCommandTimeout
|
|
})
|
|
.should('be.visible')
|
|
.and('contain.text', mpeTablesTable)
|
|
|
|
// MPE_TABLES sheet does not have data so 1 error image must be shown
|
|
hasErrorTables(1, (valid: boolean) => {
|
|
if (valid) done()
|
|
})
|
|
}
|
|
}
|
|
)
|
|
})
|
|
}
|
|
)
|
|
})
|
|
})
|
|
|
|
it('2 | Uploads Excel file with multiple sheets, 2 sheets matched with dataset, 1 matched sheet does not have data', (done) => {
|
|
attachExcelFile('multi_load_test_1.xlsx', () => {
|
|
checkHotUserDatasetTable(
|
|
'hotTableUserDataset',
|
|
[
|
|
[library, mpeXTestTable],
|
|
[library, mpeTablesTable]
|
|
],
|
|
() => {
|
|
cy.get('#continue-btn')
|
|
.trigger('click')
|
|
.then(() => {
|
|
checkIfTreeHasTables(
|
|
[`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`],
|
|
`${library}.${mpeXTestTable}`,
|
|
(includes: boolean) => {
|
|
if (includes) {
|
|
cy.get('#hotTable')
|
|
.should('be.visible')
|
|
.then(() => {
|
|
checkHotUserDatasetTable(
|
|
'hotTable',
|
|
[
|
|
['No', '1', 'more dummy data'],
|
|
[
|
|
'No',
|
|
'1',
|
|
'It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told:'
|
|
],
|
|
[
|
|
'No',
|
|
'1',
|
|
'if you can fill the unforgiving minute'
|
|
]
|
|
],
|
|
() => {
|
|
submitTables()
|
|
|
|
hasSuccessSubmits(2, (valid: boolean) => {
|
|
if (valid) done()
|
|
})
|
|
}
|
|
)
|
|
})
|
|
}
|
|
}
|
|
)
|
|
})
|
|
}
|
|
)
|
|
})
|
|
})
|
|
|
|
it('3 | Uploads Excel file with multiple sheets, 1 sheets has 2 tables', (done) => {
|
|
attachExcelFile('multi_load_test_1.xlsx', () => {
|
|
checkHotUserDatasetTable(
|
|
'hotTableUserDataset',
|
|
[
|
|
[library, mpeXTestTable],
|
|
[library, mpeTablesTable]
|
|
],
|
|
() => {
|
|
cy.get('#continue-btn')
|
|
.trigger('click')
|
|
.then(() => {
|
|
checkIfTreeHasTables(
|
|
[`${library}.${mpeXTestTable}`, `${library}.${mpeTablesTable}`],
|
|
`${library}.${mpeXTestTable}`,
|
|
(includes: boolean) => {
|
|
if (includes) {
|
|
cy.get('#hotTable')
|
|
.should('be.visible')
|
|
.then(() => {
|
|
checkHotUserDatasetTable(
|
|
'hotTable',
|
|
[
|
|
['No', '1', 'more dummy data'],
|
|
[
|
|
'No',
|
|
'1',
|
|
'It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told: It was a dark and stormy night. The wind was blowing a gale! The captain said to his mate - mate, tell us a tale. And this, is the tale he told:'
|
|
],
|
|
[
|
|
'No',
|
|
'1',
|
|
'if you can fill the unforgiving minute'
|
|
]
|
|
],
|
|
() => {
|
|
clickOnTreeNode('DC996664.MPE_TABLES', () => {
|
|
cy.wait(1000).then(() => {
|
|
cy.get('#hotTable')
|
|
.should('be.visible')
|
|
.then(() => {
|
|
checkHotUserDatasetTable(
|
|
'hotTable',
|
|
[
|
|
[
|
|
'No',
|
|
'DC914286',
|
|
'MPE_COLUMN_LEVEL_SECURITY'
|
|
],
|
|
['No', 'DC914286', 'MPE_XLMAP_INFO'],
|
|
['No', 'DC914286', 'MPE_XLMAP_RULES']
|
|
],
|
|
() => {
|
|
submitTables()
|
|
|
|
hasSuccessSubmits(
|
|
2,
|
|
(valid: boolean) => {
|
|
if (valid) done()
|
|
}
|
|
)
|
|
}
|
|
)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
)
|
|
})
|
|
}
|
|
}
|
|
)
|
|
})
|
|
}
|
|
)
|
|
})
|
|
})
|
|
|
|
// The file's own status line: size and stage. The stages are momentary, so
|
|
// the dataset lookups are held open to make "Fetching the dataset details"
|
|
// assertable (and visible in the recording).
|
|
it('4 | Shows the file size and the loading stage while the file is processed', () => {
|
|
attachExcelFile('multi_load_test_1.xlsx', () => {
|
|
cy.contains('File size:')
|
|
.should('be.visible')
|
|
.invoke('text')
|
|
.should('match', /File size:\s*[\d.]+ MB/)
|
|
|
|
// The stage line lives in the file pane until the datasets are fetched,
|
|
// then in the no-table-selected pane while the sheets are extracted, so
|
|
// both ids are asked for - the element that carries it changes.
|
|
const STAGE = '#file-loading-stage, #file-extraction-stage'
|
|
|
|
// The sheet scan is quick; from then on the file waits for the user.
|
|
cy.get(STAGE, { timeout: longerCommandTimeout })
|
|
.should('be.visible')
|
|
.and('contain.text', 'File ready to load')
|
|
|
|
// The adapter posts to /SASjsApi/stp/execute/?_program=... - a glob on
|
|
// 'execute**' does not match the trailing slash, so match on the path.
|
|
cy.intercept({ url: /SASjsApi\/stp\/execute/ }, (req) => {
|
|
req.on('response', (res) => {
|
|
res.setDelay(1500)
|
|
})
|
|
})
|
|
|
|
cy.get('#continue-btn').trigger('click')
|
|
|
|
cy.get(STAGE, { timeout: longerCommandTimeout })
|
|
.should('be.visible')
|
|
.and('contain.text', 'Fetching the dataset details')
|
|
|
|
// Once every sheet has been extracted the stage gives way to the hint.
|
|
cy.get(STAGE, { timeout: longerCommandTimeout }).should('not.exist')
|
|
cy.contains('Please select a dataset on the left').should('be.visible')
|
|
})
|
|
})
|
|
|
|
this.afterEach(() => {
|
|
colorLog(`TEST END -------------`, '#3498DB')
|
|
})
|
|
})
|
|
|
|
const attachExcelFile = (excelFilename: string, callback?: any) => {
|
|
cy.get('#browse-file')
|
|
.should('exist')
|
|
.click()
|
|
.then(() => {
|
|
cy.get('input[type="file"]#file-upload')
|
|
.attachFile(`/${fixturePath}/${excelFilename}`)
|
|
.then(() => {
|
|
if (callback) callback()
|
|
})
|
|
})
|
|
}
|
|
|
|
const checkHotUserDatasetTable = (
|
|
hotId: string,
|
|
dataToContain: any[][],
|
|
callback?: () => void
|
|
) => {
|
|
cy.get(`#${hotId}`, { timeout: longerCommandTimeout })
|
|
.find('div.ht_master.handsontable')
|
|
.find('div.wtHolder')
|
|
.find('div.wtHider')
|
|
.find('div.wtSpreader')
|
|
.find('table.htCore')
|
|
.find('tbody')
|
|
.then((data) => {
|
|
cy.wait(2000).then(() => {
|
|
for (let rowI = 0; rowI < dataToContain.length; rowI++) {
|
|
for (let colI = 0; colI < dataToContain[rowI].length; colI++) {
|
|
expect(data[0].children[rowI].children[colI]).to.contain(
|
|
dataToContain[rowI][colI]
|
|
)
|
|
}
|
|
}
|
|
|
|
if (callback) callback()
|
|
})
|
|
})
|
|
}
|
|
|
|
const clickOnTreeNode = (clickOnNode: string, callback?: () => void) => {
|
|
cy.get('.nav-tree clr-tree > clr-tree-node').then((treeNodes: any) => {
|
|
for (let node of treeNodes) {
|
|
if (node.innerText.toUpperCase().trim().includes(clickOnNode)) {
|
|
cy.get(node).trigger('click')
|
|
if (callback) callback()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const checkIfTreeHasTables = (
|
|
tables: string[],
|
|
clickOnNode?: string,
|
|
callback?: (includes: boolean) => void
|
|
) => {
|
|
cy.get('.nav-tree clr-tree > clr-tree-node').then((treeNodes: any) => {
|
|
let datasets = tables
|
|
let nodesCorrect = true
|
|
let nodeToClick
|
|
|
|
for (let node of treeNodes) {
|
|
if (!datasets.includes(node.innerText.toUpperCase().trim())) {
|
|
nodesCorrect = false
|
|
}
|
|
|
|
if (clickOnNode) {
|
|
if (node.innerText.toUpperCase().trim().includes(clickOnNode)) {
|
|
nodeToClick = node
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nodeToClick) {
|
|
cy.wait(1000)
|
|
cy.get(nodeToClick).trigger('click')
|
|
}
|
|
|
|
if (callback) callback(nodesCorrect)
|
|
})
|
|
}
|
|
|
|
const submitTables = () => {
|
|
cy.get('#submit-all').trigger('click')
|
|
cy.get('#submit-tables').trigger('click')
|
|
cy.wait(1000)
|
|
}
|
|
|
|
const hasSuccessSubmits = (
|
|
expectedNoOfSubmits: number,
|
|
callback: (valid: boolean) => void
|
|
) => {
|
|
cy.get('.nav-tree clr-tree > clr-tree-node cds-icon[status="success"]')
|
|
.should('be.visible')
|
|
.then(($nodes) => {
|
|
callback(expectedNoOfSubmits === $nodes.length)
|
|
})
|
|
}
|
|
|
|
const hasErrorTables = (
|
|
expectedNoOfErrors: number,
|
|
callback: (valid: boolean) => void
|
|
) => {
|
|
cy.get('.nav-tree clr-tree > clr-tree-node cds-icon[status="danger"]')
|
|
.should('be.visible')
|
|
.then(($nodes) => {
|
|
callback(expectedNoOfErrors === $nodes.length)
|
|
})
|
|
}
|
|
|
|
const visitPage = (url: string) => {
|
|
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
|
|
}
|
|
|
|
const colorLog = (msg: string, color: string) => {
|
|
console.log('%c' + msg, 'color:' + color + ';font-weight:bold;')
|
|
}
|