Browse Source

test(cypress): add rollup tests

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/510/head
Pranav C 3 years ago
parent
commit
ce30605f75
  1. 2
      .github/workflows/ci-cd.yml
  2. 7
      README.md
  3. 7
      cypress.json
  4. 102
      cypress/integration/common/rollup_column.js
  5. 26
      cypress/integration/common/table_column_operations_spec.js
  6. 4
      cypress/support/commands.js
  7. 2
      docker-compose-cypress.yml
  8. 6
      packages/nc-gui/components/project/spreadsheet/components/editColumn/rollupOptions.vue
  9. 5
      packages/nc-gui/components/project/spreadsheet/components/editVirtualColumn.vue
  10. 105
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/rollupCell.vue
  11. 4
      packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue
  12. 1
      packages/nc-gui/cypress.json
  13. 5
      packages/nc-gui/cypress/fixtures/example.json
  14. 36
      packages/nc-gui/cypress/integration/firsttime_run_spec.js
  15. 22
      packages/nc-gui/cypress/plugins/index.js
  16. 25
      packages/nc-gui/cypress/support/commands.js
  17. 20
      packages/nc-gui/cypress/support/index.js

2
.github/workflows/ci-cd.yml

@ -17,7 +17,7 @@ jobs:
- name: Cypress run - name: Cypress run
uses: cypress-io/github-action@v2 uses: cypress-io/github-action@v2
with: with:
start: docker-compose -f ./docker-compose-cypress.yml up start: docker-compose -f ./docker-compose-cypress.yml up -d
wait-on: 'http://localhost:3000' wait-on: 'http://localhost:3000'
wait-on-timeout: 900 wait-on-timeout: 900
docker: docker:

7
README.md

@ -27,6 +27,8 @@ Turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart-spreadshe
<br/><br/> <br/><br/>
</p> </p>
<img src="https://static.scarf.sh/a.png?x-pxid=c12a77cc-855e-4602-8a0f-614b2d0da56a" />
<a href="https://www.producthunt.com/posts/nocodb?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-nocodb" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=297536&theme=dark" alt="NocoDB - The Open Source Airtable alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/posts/nocodb?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-nocodb" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=297536&theme=dark" alt="NocoDB - The Open Source Airtable alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
# Quick try # Quick try
@ -204,7 +206,10 @@ Changes made to code automatically restart.
## Running Cypress tests locally ## Running Cypress tests locally
```shell ```shell
# run requered services by using docker compose # install dependencies(cypress)
npm install
# run required services by using docker compose
docker-compose -f ./docker-compose-cypress.yml up docker-compose -f ./docker-compose-cypress.yml up

7
cypress.json

@ -1,9 +1,10 @@
{ {
"baseUrl": "http://localhost:3000", "baseUrl": "http://localhost:3000",
"defaultCommandTimeout": 10000, "defaultCommandTimeout": 13000,
"pageLoadTimeout": 600000, "pageLoadTimeout": 600000,
"viewportWidth": 1600, "viewportWidth": 1980,
"viewportHeight": 1000, "viewportHeight": 1000,
"video": false, "video": false,
"retries": 2 "retries": 2,
"screenshotOnRunFailure": false
} }

102
cypress/integration/common/rollup_column.js

@ -0,0 +1,102 @@
const genTest = (type) => {
describe(`${type.toUpperCase()} api - Rollup column`, () => {
const colName = 'column_name' + Date.now();
const updatedColName = 'updated_name' + Date.now();
before(() => {
cy.waitForSpinners();
if (type === 'rest') {
cy.openOrCreateRestProject({
new:true
});
} else {
cy.openOrCreateGqlProject({
new:true
});
}
cy.openTableTab('Country');
});
it('Add rollup column', () => {
cy.get('.v-window-item--active .nc-grid tr > th:last button').click({force: true});
cy.get('.nc-column-name-input input').clear().type(colName)
cy.get('.nc-ui-dt-dropdown').click()
cy.getActiveMenu().contains('Rollup').click()
cy.get('.nc-rollup-table').click();
cy.getActiveMenu().contains('City').click();
cy.get('.nc-rollup-column').click();
cy.getActiveMenu().contains('CityId').click();
cy.get('.nc-rollup-fn').click();
cy.getActiveMenu().contains('count').click();
cy.get('.nc-col-create-or-edit-card').contains('Save').click()
cy
.get(`th:contains(${colName})`)
.should('exist');
cy.wait(500)
cy.get(`td[data-col="${colName}"]`).first().invoke('text').should('match', /^\s*\d+\s*$/)
})
// edit the newly created column
it('Edit table column - rename', () => {
cy.get(`th:contains(${colName}) .mdi-menu-down`)
.trigger('mouseover', {force: true})
.click({force: true})
cy.get('.nc-column-edit').click()
// rename column and verify
cy.get('.nc-column-name-input input').clear().type(updatedColName)
cy.get('.nc-col-create-or-edit-card').contains('Save').click()
cy
.get(`th:contains(${updatedColName})`)
.should('exist');
cy
.get(`th:contains(${colName})`)
.should('not.exist');
})
// delete the newly created column
it('Delete table column', () => {
cy
.get(`th:contains(${updatedColName})`)
.should('exist');
cy.get(`th:contains(${updatedColName}) .mdi-menu-down`)
.trigger('mouseover')
.click()
cy.get('.nc-column-delete').click()
cy.getActiveModal().find('button:contains(Confirm)').click()
cy
.get(`th:contains(${updatedColName})`)
.should('not.exist');
})
});
}
genTest('rest')
genTest('graphql')

26
cypress/integration/common/table_column_operations_spec.js

@ -2,6 +2,8 @@ const genTest = (type) => {
describe(`${type.toUpperCase()} api - Table Column`, () => { describe(`${type.toUpperCase()} api - Table Column`, () => {
const name = 'Table' + Date.now(); const name = 'Table' + Date.now();
const colName = 'column_name' + Date.now();
const updatedColName = 'updated_column_name' + Date.now();
before(() => { before(() => {
cy.waitForSpinners(); cy.waitForSpinners();
@ -32,10 +34,10 @@ const genTest = (type) => {
cy.get(`.project-tab:contains(${name}):visible`).should('exist') cy.get(`.project-tab:contains(${name}):visible`).should('exist')
cy.get('.v-window-item--active .nc-grid tr > th:last button').click({force: true}); cy.get('.v-window-item--active .nc-grid tr > th:last button').click({force: true});
cy.get('.nc-column-name-input input').clear().type('new_column') cy.get('.nc-column-name-input input').clear().type(colName)
cy.get('.nc-col-create-or-edit-card').contains('Save').click() cy.get('.nc-col-create-or-edit-card').contains('Save').click()
cy cy
.get('th:contains(new_column)') .get(`th:contains(${colName})`)
.should('exist'); .should('exist');
}); });
@ -44,7 +46,7 @@ const genTest = (type) => {
it('Edit table column - rename & uidt update', () => { it('Edit table column - rename & uidt update', () => {
cy.get('th:contains(new_column) .mdi-menu-down') cy.get(`th:contains(${colName}) .mdi-menu-down`)
.trigger('mouseover', {force: true}) .trigger('mouseover', {force: true})
.click({force: true}) .click({force: true})
@ -56,10 +58,10 @@ const genTest = (type) => {
cy.contains('LongText').click() cy.contains('LongText').click()
cy.get('.nc-col-create-or-edit-card').contains('Save').click() cy.get('.nc-col-create-or-edit-card').contains('Save').click()
cy.get('th[data-col="new_column"] .mdi-text-subject').should('exist') cy.get(`th[data-col="${colName}"] .mdi-text-subject`).should('exist')
cy.get('th:contains(new_column) .mdi-menu-down') cy.get(`th:contains(${colName}) .mdi-menu-down`)
.trigger('mouseover', {force: true}) .trigger('mouseover', {force: true})
.click({force: true}) .click({force: true})
@ -72,22 +74,22 @@ const genTest = (type) => {
it('Edit table column - rename', () => { it('Edit table column - rename', () => {
cy.get('th:contains(new_column) .mdi-menu-down') cy.get(`th:contains(${colName}) .mdi-menu-down`)
.trigger('mouseover', {force: true}) .trigger('mouseover', {force: true})
.click({force: true}) .click({force: true})
cy.get('.nc-column-edit').click() cy.get('.nc-column-edit').click()
// rename column and verify // rename column and verify
cy.get('.nc-column-name-input input').clear().type('updated_column') cy.get('.nc-column-name-input input').clear().type(updatedColName)
cy.get('.nc-col-create-or-edit-card').contains('Save').click() cy.get('.nc-col-create-or-edit-card').contains('Save').click()
cy cy
.get('th:contains(updated_column)') .get(`th:contains(${updatedColName})`)
.should('exist'); .should('exist');
cy cy
.get('th:contains(new_column)') .get(`th:contains(${colName})`)
.should('not.exist'); .should('not.exist');
@ -97,10 +99,10 @@ const genTest = (type) => {
// delete the newly created column // delete the newly created column
it('Delete table column', () => { it('Delete table column', () => {
cy cy
.get('th:contains(updated_column)') .get(`th:contains(${updatedColName})`)
.should('exist'); .should('exist');
cy.get('th:contains(updated_column) .mdi-menu-down') cy.get(`th:contains(${updatedColName}) .mdi-menu-down`)
.trigger('mouseover') .trigger('mouseover')
.click() .click()
@ -109,7 +111,7 @@ const genTest = (type) => {
cy cy
.get('th:contains(updated_column)') .get(`th:contains(${updatedColName})`)
.should('not.exist'); .should('not.exist');
}) })

4
cypress/support/commands.js

@ -162,6 +162,10 @@ Cypress.Commands.add("getActiveMenu", () => {
return cy.get('.menuable__content__active') return cy.get('.menuable__content__active')
}); });
Cypress.Commands.add("getActiveModal", () => {
return cy.get('.v-dialog__content--active')
});
Cypress.Commands.add('createTable', (name) => { Cypress.Commands.add('createTable', (name) => {
cy.get('.add-btn').click(); cy.get('.add-btn').click();

2
docker-compose-cypress.yml

@ -1,7 +1,7 @@
version: "3.5" version: "3.5"
services: services:
db80: xc-mysql-sakila-db:
network_mode: host network_mode: host
image: mysql:8.0 image: mysql:8.0
restart: always restart: always

6
packages/nc-gui/components/project/spreadsheet/components/editColumn/rollupOptions.vue

@ -7,7 +7,7 @@
ref="input" ref="input"
v-model="rollup.table" v-model="rollup.table"
outlined outlined
class="caption" class="caption nc-rollup-table"
hide-details="auto" hide-details="auto"
label="Child Table" label="Child Table"
:full-width="false" :full-width="false"
@ -30,7 +30,7 @@
ref="input" ref="input"
v-model="rollup.column" v-model="rollup.column"
outlined outlined
class="caption" class="caption nc-rollup-column"
hide-details="auto" hide-details="auto"
label="Child column" label="Child column"
:full-width="false" :full-width="false"
@ -47,7 +47,7 @@
ref="aggrInput" ref="aggrInput"
v-model="rollup.fn" v-model="rollup.fn"
outlined outlined
class="caption" class="caption nc-rollup-fn"
hide-details="auto" hide-details="auto"
label="Aggregate function" label="Aggregate function"
:full-width="false" :full-width="false"

5
packages/nc-gui/components/project/spreadsheet/components/editVirtualColumn.vue

@ -4,7 +4,7 @@
max-width="400px" max-width="400px"
max-height="95vh" max-height="95vh"
style="overflow: auto" style="overflow: auto"
class="elevation-0 card" class="elevation-0 card nc-col-create-or-edit-card"
> >
<v-form v-model="valid"> <v-form v-model="valid">
<v-container fluid @click.stop.prevent> <v-container fluid @click.stop.prevent>
@ -24,7 +24,7 @@
v-model="newColumn._cn" v-model="newColumn._cn"
hide-details="auto" hide-details="auto"
color="primary" color="primary"
class="caption" class="caption nc-column-name-input"
label="Column name" label="Column name"
:rules="[ :rules="[
v => !!v || 'Required', v => !!v || 'Required',
@ -86,6 +86,7 @@ export default {
this.newColumn = {} this.newColumn = {}
}, },
async save() { async save() {
// todo: rollup update
try { try {
if (this.column.formula) { if (this.column.formula) {
await this.$refs.formula.update() await this.$refs.formula.update()

105
packages/nc-gui/components/project/spreadsheet/components/virtualCell/rollupCell.vue

@ -8,115 +8,14 @@
export default { export default {
name: 'RollupCell', name: 'RollupCell',
components: { }, components: {},
props: { props: {
meta: [Object], meta: [Object],
column: [Object], column: [Object],
nodes: [Object], nodes: [Object],
row: [Object] row: [Object]
},
data: () => ({
lookupListModal: false
}),
computed: {
// todo : optimize
lookupApi() {
return this.column && this.$ncApis.get({
env: this.nodes.env,
dbAlias: this.nodes.dbAlias,
table: this.column.lk.ltn
})
},
lookUpMeta() {
return this.$store.state.meta.metas[this.column.lk.ltn]
},
assocMeta() {
return this.column.lk.type === 'mm' && this.$store.state.meta.metas[this.column.lk.vtn]
},
lookUpColumnAlias() {
if (!this.lookUpMeta || !this.column.lk.lcn) {
return
}
return (this.$store.state.meta.metas[this.column.lk.ltn].columns.find(cl => cl.cn === this.column.lk.lcn) || {})._cn
},
lookUpColumn() {
if (!this.lookUpMeta || !this.column.lk.lcn) {
return
}
return (this.$store.state.meta.metas[this.column.lk.ltn].columns.find(cl => cl.cn === this.column.lk.lcn) || {})
},
localValueObj() {
if (!this.column || !this.row) {
return null
}
switch (this.column.lk.type) {
case 'mm':
return this.row[`${this.column.lk._ltn}MMList`]
case 'hm':
return this.row[`${this.column.lk._ltn}List`]
case 'bt':
return this.row[`${this.column.lk._ltn}Read`]
default:
return null
}
},
localValue() {
if (!this.localValueObj || !this.lookUpColumnAlias) {
return null
}
if (Array.isArray(this.localValueObj)) {
return this.localValueObj.map(o => o[this.lookUpColumnAlias])
}
return [this.localValueObj[this.lookUpColumnAlias]]
},
queryParams() {
switch (this.column.lk.type) {
case 'bt':
return { where: `(${this.lookUpMeta.columns.find(c => c.cn === this.column.lk.rcn)._cn},eq,${this.row[this.meta.columns.find(c => c.cn === this.column.lk.cn)._cn]})` }
case 'hm':
return { where: `(${this.lookUpMeta.columns.find(c => c.cn === this.column.lk.cn)._cn},eq,${this.row[this.meta.columns.find(c => c.cn === this.column.lk.rcn)._cn]})` }
case 'mm':
return this.assocMeta
? {
conditionGraph: {
[this.assocMeta.tn]: {
relationType: 'hm',
[this.assocMeta.columns.find(c => c.cn === this.column.lk.vcn).cn]: {
eq: this.row[this.meta.columns.find(c => c.cn === this.column.lk.cn)._cn]
}
}
}
}
: {}
default:
return {}
}
}
},
created() {
this.loadLookupMeta()
},
methods: {
async loadLookupMeta() {
if (!this.lookUpMeta) {
await this.$store.dispatch('meta/ActLoadMeta', {
env: this.nodes.env,
dbAlias: this.nodes.dbAlias,
tn: this.column.lk.ltn
})
}
if (this.column.lk.type === 'mm' && !this.assocMeta) {
await this.$store.dispatch('meta/ActLoadMeta', {
env: this.nodes.env,
dbAlias: this.nodes.dbAlias,
tn: this.column.lk.vtn
})
}
},
showLookupListModal() {
this.lookupListModal = true
}
} }
} }
</script> </script>

4
packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue

@ -58,7 +58,7 @@
</template> </template>
<v-list dense> <v-list dense>
<v-list-item v-if="!column.lk" dense @click="editColumnMenu = true"> <v-list-item v-if="!column.lk" dense @click="editColumnMenu = true">
<x-icon small class="mr-1" color="primary"> <x-icon small class="mr-1 nc-column-edit" color="primary">
mdi-pencil mdi-pencil
</x-icon> </x-icon>
<span class="caption">Edit</span> <span class="caption">Edit</span>
@ -73,7 +73,7 @@
</v-tooltip> </v-tooltip>
</v-list-item> --> </v-list-item> -->
<v-list-item @click="columnDeleteDialog = true"> <v-list-item @click="columnDeleteDialog = true">
<x-icon small class="mr-1" color="error"> <x-icon small class="mr-1 nc-column-delete" color="error">
mdi-delete-outline mdi-delete-outline
</x-icon> </x-icon>
<span class="caption">Delete</span> <span class="caption">Delete</span>

1
packages/nc-gui/cypress.json

@ -1 +0,0 @@
{}

5
packages/nc-gui/cypress/fixtures/example.json

@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

36
packages/nc-gui/cypress/integration/firsttime_run_spec.js

@ -1,36 +0,0 @@
/* eslint-disable */
describe('My First Test', () => {
it('Does not do much!', () => {
describe('My First Test', () => {
it('Sign Up / Sign In', () => {
cy.visit('http://localhost:3000')
cy.get('body').then(($body) => {
if ($body.find('.let-us-begin').length > 0) {
cy.contains('Let\'s Begin').click()
cy.wait(1000)
cy.get('input[type="text"]').type('pranavc@gmail.com')
cy.get('input[type="password"]').type('Password123.')
cy.contains('Sign Up').click()
} else {
cy.get('input[type="text"]').type('pranavc@gmail.com')
cy.get('input[type="password"]').type('Password123.')
cy.contains('Sign In').click()
}
})
})
it('Create Project', () => {
cy.wait(1000)
cy.contains('New Project').trigger('onmouseover').trigger('mouseenter')
cy.wait(1500)
cy.get('.create-external-db-project').click()
cy.wait(5500)
cy.get('.database-field input').click().clear().type('sakila')
cy.contains('Test Database Connection').click()
cy.wait(1500)
cy.contains('Ok & Save Project').click()
})
})
})
})

22
packages/nc-gui/cypress/plugins/index.js

@ -1,22 +0,0 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

25
packages/nc-gui/cypress/support/commands.js

@ -1,25 +0,0 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

20
packages/nc-gui/cypress/support/index.js

@ -1,20 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
Loading…
Cancel
Save