Confirming the "source file will be discarded" warning coerced every numeric column of the preview with parseInt, which truncated the fraction: 0.901045951946194 became 0, and 1234.56 became 1234. parseFloat converts the same values without dropping precision, and a cell the parser could not read as a number is left as text so the editor's numeric validation flags it instead of showing NaN. Adds an Excel spec case that reads the first data row before and after the switch to edit mode and asserts the decimal value survives.
647 lines
19 KiB
TypeScript
647 lines
19 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/'
|
|
|
|
context('excel tests: ', function () {
|
|
this.beforeAll(() => {
|
|
cy.visit(`${hostUrl}/SASLogon/logout`)
|
|
cy.loginAndUpdateValidKey(true)
|
|
})
|
|
|
|
this.beforeEach(() => {
|
|
cy.visit(hostUrl + appLocation)
|
|
|
|
visitPage('home')
|
|
|
|
colorLog(
|
|
`TEST START ---> ${
|
|
Cypress.mocha.getRunner().suite.ctx.currentTest.title
|
|
}`,
|
|
'#3498DB'
|
|
)
|
|
})
|
|
|
|
it('1 | Uploads regular Excel file', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('regular_excel.xlsx', () => {
|
|
submitExcel()
|
|
rejectExcel(done)
|
|
})
|
|
})
|
|
|
|
it('2 | Uploads Excel with data on the 7th tab', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('7th_tab_excel.xlsx', () => {
|
|
submitExcel()
|
|
rejectExcel(done)
|
|
})
|
|
})
|
|
|
|
it('3 | Uploads Excel with missing columns (should fail)', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('missing_columns_excel.xlsx', () => {
|
|
cy.get('.abortMsg', { timeout: longerCommandTimeout })
|
|
.should('exist')
|
|
.then((elements: any) => {
|
|
if (elements[0]) {
|
|
if (elements[0].innerText.toLowerCase().includes('missing')) done()
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
it('4 | Uploads Excel with formulas', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
|
|
|
|
attachExcelFile('formulas_excel.xlsx', () => {
|
|
checkResultOfFormulaUpload(done)
|
|
})
|
|
})
|
|
|
|
it('5 | Uploads Excel with no data rows', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('nodata_rows_excel.xlsx', () => {
|
|
cy.get('.abortMsg', { timeout: longerCommandTimeout })
|
|
.should('exist')
|
|
.then((elements: any) => {
|
|
if (elements[0]) {
|
|
if (
|
|
elements[0].innerText
|
|
.toLowerCase()
|
|
.includes('no relevant data found')
|
|
)
|
|
done()
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
it('6 | Uploads Excel with a table that is surrounded by other data', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('surrounded_data_excel.xlsx', () => {
|
|
submitExcel()
|
|
rejectExcel(done)
|
|
})
|
|
})
|
|
|
|
it('7 | Uploads Excel with a extra columns in the middle', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('extra_column_excel.xlsx', () => {
|
|
submitExcel()
|
|
rejectExcel(done)
|
|
})
|
|
})
|
|
|
|
it('8 | Uploads Excel with a duplicate column', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('duplicate_column_excel.xlsx', () => {
|
|
submitExcel()
|
|
rejectExcel(done)
|
|
})
|
|
})
|
|
|
|
it('9 | Uploads Excel with a duplicate row', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('duplicate_row_excel.xlsx', () => {
|
|
submitExcel(() => {
|
|
cy.get('.duplicate-keys-modal', { timeout: longerCommandTimeout })
|
|
.should('exist')
|
|
.then((elements: any) => {
|
|
if (elements[0]) {
|
|
if (elements[0].innerText.toLowerCase().includes('duplicates'))
|
|
done()
|
|
}
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
it('10 | Uploads Excel with a mixed content', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('mixed_content_excel.xlsx', () => {
|
|
submitExcel(() => {
|
|
cy.get('.modal-body').then((modalBody: any) => {
|
|
if (
|
|
modalBody[0].innerHTML
|
|
.toLowerCase()
|
|
.includes(`invalid values are present`)
|
|
) {
|
|
done()
|
|
}
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
it('11 | Uploads Excel with a blank columns', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('blank_columns_excel.xlsx', () => {
|
|
cy.get('.abortMsg', { timeout: longerCommandTimeout })
|
|
.should('exist')
|
|
.then((elements: any) => {
|
|
if (elements[0]) {
|
|
if (elements[0].innerText.toLowerCase().includes('missing')) done()
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
it('12 | Uploads Excel xls extension', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('regular_excel_xls.xls', () => {
|
|
submitExcel()
|
|
rejectExcel(done)
|
|
})
|
|
})
|
|
|
|
// For some strange reason this file breaks cypress. When uploaded manually in DC it is working.
|
|
// it('13 | Uploads Excel xlsm extension', (done) => {
|
|
// openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
// attachExcelFile('regular_excel_macro.xlsm', () => {
|
|
// submitExcel()
|
|
// rejectExcel(done)
|
|
// })
|
|
// })
|
|
|
|
it('14 | Uploads Excel with composite primary key', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
|
|
|
|
attachExcelFile('MPE_DATADICTIONARY_composite_keys.xlsx', () => {
|
|
submitExcel()
|
|
rejectExcel(done)
|
|
})
|
|
})
|
|
|
|
it('15 | Uploads Excel with missing row (empty table)', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
|
|
|
|
attachExcelFile('MPE_DATADICTIONARY_missing_row.xlsx', () => {
|
|
cy.get('.abortMsg', { timeout: longerCommandTimeout })
|
|
.should('exist')
|
|
.then((elements: any) => {
|
|
if (elements[0]) {
|
|
if (
|
|
elements[0].innerText
|
|
.toLowerCase()
|
|
.includes('no relevant data found')
|
|
)
|
|
done()
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
it('16 | Uploads Excel with merged cells', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_datadictionary')
|
|
|
|
attachExcelFile('MPE_DATADICTIONARY_merged_cells.xlsx', () => {
|
|
submitExcel()
|
|
rejectExcel(done)
|
|
})
|
|
})
|
|
|
|
it('17 | Check uploaded values from excel with xls extension', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('regular_excel_xls.xls', () => {
|
|
checkResultOfXLSUpload(done)
|
|
})
|
|
})
|
|
|
|
it('18 | Uploads Excel with missing row (empty table)', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('blank_column_with_header.xlsx', () => {
|
|
cy.get('.btn-upload-preview', { timeout: 60000 })
|
|
.should('be.visible')
|
|
.then(() => {
|
|
cy.get('#hotTable', { timeout: 30000 })
|
|
.find('div.ht_master.handsontable')
|
|
.find('div.wtHolder')
|
|
.find('div.wtHider')
|
|
.find('div.wtSpreader')
|
|
.find('table.htCore')
|
|
.find('tbody')
|
|
.then((data) => {
|
|
let allEmpty = true
|
|
|
|
for (let col = 0; col < data[0].children.length; col++) {
|
|
const cell: any = data[0].children[col].children[5]
|
|
|
|
if (cell.innerText !== '') {
|
|
allEmpty = false
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if (allEmpty) done()
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
it('19 | Uploads Excel with data on random sheet surrounded with all empty cells', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('surrounded_data_all_cells_empty_excel.xlsx', () => {
|
|
checkResultOfXLSUpload(done)
|
|
})
|
|
})
|
|
|
|
it('20 | Uploads Excel with data surrounded with empty cells ', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('surrounded_data_empty_cells_excel.xlsx', () => {
|
|
checkResultOfXLSUpload(done)
|
|
})
|
|
})
|
|
|
|
it('21 | Uploads regular Excel file with first row marked for Delete (yes)', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
attachExcelFile('regular_excel_with_delete.xlsx', () => {
|
|
cy.get('.btn-upload-preview', { timeout: 60000 })
|
|
.should('be.visible')
|
|
.then(() => {
|
|
cy.get('#hotTable', { timeout: 30000 })
|
|
.find('div.ht_master.handsontable')
|
|
.find('div.wtHolder')
|
|
.find('div.wtHider')
|
|
.find('div.wtSpreader')
|
|
.find('table.htCore')
|
|
.find('tbody')
|
|
.then((data: JQuery<HTMLTableSectionElement>) => {
|
|
const firstRowFirstCol: Partial<HTMLElement> =
|
|
data[0].children[0].children[1]
|
|
|
|
if (
|
|
firstRowFirstCol.innerText &&
|
|
!firstRowFirstCol.innerText.toLowerCase().includes('yes')
|
|
) {
|
|
done('Delete? column from file not applied')
|
|
}
|
|
})
|
|
.then(() => {
|
|
submitExcel()
|
|
rejectExcel(done)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
it('22 | Uploads password protected Excel and unlocks with correct password', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
cy.get('.buttonBar button:last-child')
|
|
.should('exist')
|
|
.click()
|
|
.then(() => {
|
|
cy.get('input[type="file"]#file-upload')
|
|
.attachFile(`/${fixturePath}/regular_excel_password.xlsx`)
|
|
.then(() => {
|
|
// Wait for password modal to appear
|
|
cy.get('#filePasswordInput', { timeout: 10000 })
|
|
.should('be.visible')
|
|
.type('123123')
|
|
|
|
// Click Unlock button
|
|
cy.get('.btn.btn-success-outline').should('not.be.disabled').click()
|
|
|
|
// Click away the overlay
|
|
cy.get('.modal-footer .btn.btn-primary', { timeout: 5000 }).click()
|
|
|
|
// Verify file loads successfully
|
|
cy.get('.btn-upload-preview', { timeout: 60000 })
|
|
.should('be.visible')
|
|
.then(() => {
|
|
submitExcel()
|
|
rejectExcel(done)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
it('23 | Uploads password protected Excel and handles wrong password', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
cy.get('.buttonBar button:last-child')
|
|
.should('exist')
|
|
.click()
|
|
.then(() => {
|
|
cy.get('input[type="file"]#file-upload')
|
|
.attachFile(`/${fixturePath}/regular_excel_password.xlsx`)
|
|
.then(() => {
|
|
// First attempt: Enter wrong password
|
|
cy.get('#filePasswordInput', { timeout: 10000 })
|
|
.should('be.visible')
|
|
.type('wrongpassword')
|
|
|
|
cy.get('.btn.btn-success-outline').should('not.be.disabled').click()
|
|
|
|
// Verify error message appears
|
|
cy.get('.modal-footer .color-red', { timeout: 10000 })
|
|
.should('be.visible')
|
|
.should('contain', "Sorry that didn't work, try again.")
|
|
|
|
// Modal should still be open for retry
|
|
cy.get('#filePasswordInput')
|
|
.should('be.visible')
|
|
.clear()
|
|
.type('123123')
|
|
|
|
// Second attempt: Enter correct password
|
|
cy.get('.btn.btn-success-outline').should('not.be.disabled').click()
|
|
|
|
// Click away the overlay
|
|
cy.get('.modal-footer .btn.btn-primary', { timeout: 5000 }).click()
|
|
|
|
// Verify file loads successfully
|
|
cy.get('.btn-upload-preview', { timeout: 60000 })
|
|
.should('be.visible')
|
|
.then(() => {
|
|
submitExcel()
|
|
rejectExcel(done)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
it('24 | Uploads Excel with leading whitespace in header row (should still succeed)', (done) => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
// Every header except the first has a leading space, matching a
|
|
// real-world defect from copy-pasting between spreadsheets/systems.
|
|
// Header matching must tolerate
|
|
// this rather than reporting the columns missing and aborting.
|
|
attachExcelFile('leading_whitespace_header_excel.xlsx', () => {
|
|
submitExcel()
|
|
rejectExcel(done)
|
|
})
|
|
})
|
|
|
|
it('25 | Keeps decimals when the upload preview becomes a manual edit', () => {
|
|
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
|
|
|
// Entering manual edit mode discards the source file but keeps the values
|
|
// parsed from it. SOME_NUM holds 0.901045951946194 in the first data row
|
|
// of the fixture, so a truncated or zeroed cell fails the assertion.
|
|
attachExcelFile('regular_excel.xlsx', () => {
|
|
getFirstRowCells().then((previewCells) => {
|
|
expect(previewCells).to.include(decimalSomeNum)
|
|
|
|
cy.get('.btn-upload-preview.btn-primary').click()
|
|
cy.get('.modal-footer .btn-success-outline').click()
|
|
|
|
getFirstRowCells().then((editCells) => {
|
|
expect(editCells).to.include(decimalSomeNum)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
// Large files break Cypress
|
|
|
|
// it ('? | Uploads Excel with size of 5MB', (done) => {
|
|
// attachExcelFile('5mb_excel.xlsx', () => {
|
|
// submitExcel();
|
|
// rejectExcel(done);
|
|
// });
|
|
// })
|
|
|
|
// it ('? | Uploads Excel with size of 15MB', (done) => {
|
|
// attachExcelFile('15mb_excel.xlsx', () => {
|
|
// submitExcel();
|
|
// rejectExcel(done);
|
|
// });
|
|
// })
|
|
|
|
//Large files tests end
|
|
|
|
this.afterEach(() => {
|
|
colorLog(`TEST END -------------`, '#3498DB')
|
|
})
|
|
})
|
|
|
|
const openTableFromTree = (libNameIncludes: string, tablename: string) => {
|
|
cy.get('.app-loading', { timeout: longerCommandTimeout })
|
|
.should('not.exist')
|
|
.then(() => {
|
|
cy.get('.nav-tree 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('.clr-tree-node-content-container > button').click()
|
|
|
|
cy.get('.clr-treenode-link').then((innerNodes: any) => {
|
|
for (let innerNode of innerNodes) {
|
|
if (innerNode.innerText.toLowerCase().includes(tablename)) {
|
|
innerNode.click()
|
|
break
|
|
}
|
|
}
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
const attachExcelFile = (excelFilename: string, callback?: any) => {
|
|
cy.get('.buttonBar button:last-child')
|
|
.should('exist')
|
|
.click()
|
|
.then(() => {
|
|
cy.get('input[type="file"]#file-upload')
|
|
.attachFile(`/${fixturePath}/${excelFilename}`)
|
|
.then(() => {
|
|
cy.get('.clr-abort-modal .modal-title').then((modalTitle) => {
|
|
if (!modalTitle[0].innerHTML.includes('Abort Message')) {
|
|
cy.get('.modal-footer .btn.btn-primary').then((modalBtn) => {
|
|
modalBtn.click()
|
|
if (callback) callback()
|
|
})
|
|
} else {
|
|
if (callback) callback()
|
|
}
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
const decimalSomeNum = '0.901045951946194'
|
|
|
|
// The row's cell texts, without depending on column positions: the preview
|
|
// grid renders row-status and delete cells in an order that varies.
|
|
const getFirstRowCells = () =>
|
|
cy
|
|
.get('#hotTable', { timeout: longerCommandTimeout })
|
|
.find('div.ht_master.handsontable tbody tr')
|
|
.eq(0)
|
|
.then(($row) =>
|
|
Array.from($row[0].children).map((cell: any) => cell.innerText.trim())
|
|
)
|
|
|
|
const submitExcel = (callback?: any) => {
|
|
cy.get('.buttonBar button.preview-submit', { timeout: longerCommandTimeout })
|
|
.click()
|
|
.then(() => {
|
|
if (callback) callback()
|
|
})
|
|
}
|
|
|
|
const rejectExcel = (callback?: any) => {
|
|
cy.get('button', { timeout: longerCommandTimeout })
|
|
.should('contain', 'Approve')
|
|
.then((allButtons: any) => {
|
|
for (let approvalButton of allButtons) {
|
|
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
|
approvalButton.click()
|
|
break
|
|
}
|
|
}
|
|
|
|
cy.get('button.btn-danger')
|
|
.should('exist')
|
|
.should('not.be.disabled')
|
|
.click()
|
|
.then(() => {
|
|
cy.get('.modal-footer button.btn-success-outline')
|
|
.click()
|
|
.then(() => {
|
|
cy.get('app-history')
|
|
.should('exist')
|
|
.then(() => {
|
|
if (callback) callback()
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
const acceptExcel = (callback?: any) => {
|
|
cy.get('button', { timeout: longerCommandTimeout })
|
|
.should('contain', 'Approve')
|
|
.then((allButtons: any) => {
|
|
for (let approvalButton of allButtons) {
|
|
if (approvalButton.innerText.toLowerCase().includes('approve')) {
|
|
approvalButton.click()
|
|
break
|
|
}
|
|
}
|
|
|
|
cy.get('#acceptBtn')
|
|
.should('exist')
|
|
.should('not.be.disabled')
|
|
.click()
|
|
.then(() => {
|
|
if (callback) {
|
|
callback()
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
const checkResultOfFormulaUpload = (callback?: any) => {
|
|
cy.get('#hotTable', { timeout: longerCommandTimeout })
|
|
.find('div.ht_master.handsontable')
|
|
.find('div.wtHolder')
|
|
.find('div.wtHider')
|
|
.find('div.wtSpreader')
|
|
.find('table.htCore')
|
|
.find('tbody')
|
|
.then((data) => {
|
|
const cell: any = data[0].children[0].children[5]
|
|
expect(cell.innerText).to.equal('2')
|
|
if (callback) callback()
|
|
})
|
|
}
|
|
|
|
const checkResultOfXLSUpload = (callback?: any) => {
|
|
// Config-default width — wide enough that the date/datetime/time columns are
|
|
// not virtualized away (the old 1280 width hid the trailing time columns).
|
|
cy.viewport(1600, 900)
|
|
cy.get('#hotTable', { timeout: 30000 })
|
|
.find('div.ht_master.handsontable')
|
|
.find('div.wtHolder')
|
|
.find('div.wtHider')
|
|
.find('div.wtSpreader')
|
|
.find('table.htCore')
|
|
.find('tbody')
|
|
.then((data) => {
|
|
let cell: any = data[0].children[0].children[2]
|
|
|
|
expect(cell.innerText).to.equal('0')
|
|
cell = data[0].children[0].children[3]
|
|
expect(cell.innerText).to.equal('this is dummy data changed in excel')
|
|
cell = data[0].children[0].children[4]
|
|
expect(cell.innerText).to.equal('▼\nOption 1')
|
|
cell = data[0].children[0].children[5]
|
|
expect(cell.innerText).to.equal('42')
|
|
// Cells render the raw stored ISO string verbatim (locale-independent;
|
|
// see registerIntlCellTypes). date -> YYYY-MM-DD, datetime ->
|
|
// YYYY-MM-DD HH:mm:ss, time -> HH:mm:ss.
|
|
cell = data[0].children[0].children[6]
|
|
expect(cell.innerText).to.equal('1960-02-12')
|
|
cell = data[0].children[0].children[7]
|
|
expect(cell.innerText).to.equal('1960-01-01 00:00:42')
|
|
cell = data[0].children[0].children[8]
|
|
expect(cell.innerText).to.equal('00:00:42')
|
|
|
|
if (callback) callback()
|
|
})
|
|
|
|
cy.get('#hotTable', { timeout: 30000 })
|
|
.find('div.ht_master.handsontable')
|
|
.find('div.wtHolder')
|
|
.scrollTo('right')
|
|
.find('div.wtHider')
|
|
.find('div.wtSpreader')
|
|
.find('table.htCore')
|
|
.find('tbody')
|
|
.then((data) => {
|
|
let cell: any = data[0].children[0].children[1]
|
|
|
|
cell = data[0].children[0].children[9]
|
|
|
|
expect(cell.innerText).to.equal('44')
|
|
|
|
if (callback) callback()
|
|
})
|
|
}
|
|
|
|
const visitPage = (url: string) => {
|
|
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
|
|
}
|
|
|
|
const colorLog = (msg: string, color: string) => {
|
|
console.log('%c' + msg, 'color:' + color + ';font-weight:bold;')
|
|
}
|