diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 21d735fcb..f4c17b64e 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -32,4 +32,5 @@ jobs: - run: yarn test env: CI: true - name: Budibase CI \ No newline at end of file + name: Budibase CI + - run: yarn test:e2e:ci diff --git a/lerna.json b/lerna.json index 8b2066df4..c6a99591d 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.1.13", + "version": "0.1.17", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/package.json b/package.json index 8185d0bbb..148924b6d 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,15 @@ "build": "lerna run build", "initialise": "lerna run initialise", "publishdev": "lerna run publishdev", - "publishnpm": "yarn build && lerna publish", + "publishnpm": "yarn build && lerna publish --force-publish", "clean": "lerna clean", "dev": "node ./scripts/symlinkDev.js && lerna run --parallel --stream dev:builder", "test": "lerna run test", "lint": "eslint packages", "lint:fix": "eslint --fix packages", "format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\"", - "test:e2e": "lerna run cy:test" + "test:e2e": "lerna run cy:test", + "test:e2e:ci": "lerna run cy:ci" }, "dependencies": { "@material/icon-button": "4.0.0", diff --git a/packages/builder/.gitignore b/packages/builder/.gitignore index 49444a4fc..69f2f5a00 100644 --- a/packages/builder/.gitignore +++ b/packages/builder/.gitignore @@ -2,7 +2,6 @@ /node_modules/ node_modules_win package-lock.json -yarn.lock release/ dist/ cypress/screenshots diff --git a/packages/builder/cypress.json b/packages/builder/cypress.json index 477d0a6dc..a68cea053 100644 --- a/packages/builder/cypress.json +++ b/packages/builder/cypress.json @@ -1,4 +1,5 @@ { - "baseUrl": "http://localhost:4001/_builder/", - "video": false -} \ No newline at end of file + "baseUrl": "http://localhost:4001/_builder/", + "video": true, + "projectId": "bmbemn" +} diff --git a/packages/builder/cypress/fixtures/apikey.json b/packages/builder/cypress/fixtures/apikey.json new file mode 100644 index 000000000..1def14ed6 --- /dev/null +++ b/packages/builder/cypress/fixtures/apikey.json @@ -0,0 +1,3 @@ +{ + "budibase": "CB373643-3FC4-4902-9E31-449C0ED066B6" +} \ No newline at end of file diff --git a/packages/builder/cypress/integration/createApp.spec.js b/packages/builder/cypress/integration/createApp.spec.js index 07a8687ea..5382aa04c 100644 --- a/packages/builder/cypress/integration/createApp.spec.js +++ b/packages/builder/cypress/integration/createApp.spec.js @@ -1,6 +1,7 @@ context('Create an Application', () => { beforeEach(() => { + cy.server() cy.visit('localhost:4001/_builder') }) diff --git a/packages/builder/cypress/integration/createComponents.spec.js b/packages/builder/cypress/integration/createComponents.spec.js index cedd8fefe..2e247f711 100644 --- a/packages/builder/cypress/integration/createComponents.spec.js +++ b/packages/builder/cypress/integration/createComponents.spec.js @@ -1,10 +1,11 @@ -context('Create Components', () => { +xcontext('Create Components', () => { before(() => { + cy.server() cy.visit('localhost:4001/_builder') // https://on.cypress.io/type cy.createApp('Model App', 'Model App Description') - cy.createModel('dog', 'name', 'age') + cy.createTable('dog', 'name', 'age') cy.addRecord('bob', '15') }) @@ -22,8 +23,8 @@ context('Create Components', () => { }) it('change the font size of the headline', () => { cy.contains('Typography').click() - cy.get('input[name="font-size"]') - .type('60px') + cy.get('[data-cy=font-size-prop-control]').click() + cy.contains("60px").click() cy.contains('Design').click() getIframeBody().contains('An Amazing headline!').should('have.css', 'font-size', '60px') diff --git a/packages/builder/cypress/integration/createModel.spec.js b/packages/builder/cypress/integration/createModel.spec.js deleted file mode 100644 index 830f5e7a4..000000000 --- a/packages/builder/cypress/integration/createModel.spec.js +++ /dev/null @@ -1,22 +0,0 @@ -context('Create a Model', () => { - - before(() => { - cy.visit('localhost:4001/_builder') - // https://on.cypress.io/type - cy.createApp('Model App', 'Model App Description') - }) - - // https://on.cypress.io/interacting-with-elements - it('should create a new model', () => { - - cy.createModel('dog', 'name', 'age') - - // Check if model exists - cy.get('.title').should('have.text', 'dog') - }) - it('should add a record', () => { - cy.addRecord('bob', '15') - - cy.contains('bob').should('have.text', 'bob') - }) -}) diff --git a/packages/builder/cypress/integration/createTable.spec.js b/packages/builder/cypress/integration/createTable.spec.js new file mode 100644 index 000000000..c744eb7e4 --- /dev/null +++ b/packages/builder/cypress/integration/createTable.spec.js @@ -0,0 +1,70 @@ +context('Create a Table', () => { + before(() => { + cy.visit('localhost:4001/_builder') + cy.createApp('Table App', 'Table App Description') + }) + + it('should create a new Table', () => { + cy.createTable('dog') + + // Check if Table exists + cy.get('.title').should('have.text', 'dog') + }) + + it('adds a new column to the table', () => { + cy.addColumn('dog', 'name', 'Plain Text') + + cy.contains('name').should("be.visible") + }) + + it('creates a record in the table', () => { + cy.addRecord(["Rover"]) + + cy.contains('Rover').should("be.visible") + }) + + it('updates a column on the table', () => { + cy.contains("name").click() + cy.get("[data-cy='edit-column-header']").click() + + cy.get("[placeholder=Name]").type("updated") + cy.get("select").select("Plain Text") + + cy.contains("Save Column").click() + + cy.contains('nameupdated').should('have.text', 'nameupdated ') + }) + + it('edits a record', () => { + cy.get("tbody .ri-more-line").click() + cy.get("[data-cy=edit-row]").click() + cy.get(".actions input").type("updatedRecord") + cy.contains("Save").click() + + cy.contains('updatedRecord').should('have.text', 'updatedRecord') + }) + + it('deletes a record', () => { + cy.get("tbody .ri-more-line").click() + cy.get("[data-cy=delete-row]").click() + cy.get(".modal-actions").contains("Delete").click() + + cy.contains('updatedRecord').should('not.exist') + }) + + it('deletes a column', () => { + cy.contains("name").click() + cy.get("[data-cy='delete-column-header']").click() + + cy.contains('nameupdated').should('not.exist') + }) + + it('deletes a table', () => { + cy.contains("div", "dog").get(".ri-more-line").click() + cy.get("[data-cy=delete-table]").click() + cy.get(".modal-actions").contains("Delete").click() + + cy.contains('dog').should('not.exist') + }) + +}) diff --git a/packages/builder/cypress/integration/createUser.spec.js b/packages/builder/cypress/integration/createUser.spec.js index 80d1a51d8..192cdae31 100644 --- a/packages/builder/cypress/integration/createUser.spec.js +++ b/packages/builder/cypress/integration/createUser.spec.js @@ -1,6 +1,7 @@ context('Create a User', () => { before(() => { + cy.server() cy.visit('localhost:4001/_builder') // https://on.cypress.io/type cy.createApp('User App', 'This app is used to test user creation') @@ -8,12 +9,9 @@ context('Create a User', () => { // https://on.cypress.io/interacting-with-elements it('should create a user', () => { - // Close Model modal that shows up after creating an app - cy.get('.close').click() - cy.createUser('bbuser', 'test', 'ADMIN') // Check to make sure user was created! - cy.contains('bbuser').should('have.text', 'bbuser') + cy.get("input[disabled]").should('have.value', 'bbuser') }) }) diff --git a/packages/builder/cypress/integration/createWorkflow.spec.js b/packages/builder/cypress/integration/createWorkflow.spec.js index 918872198..f341bf86b 100644 --- a/packages/builder/cypress/integration/createWorkflow.spec.js +++ b/packages/builder/cypress/integration/createWorkflow.spec.js @@ -1,6 +1,7 @@ -context('Create a workflow', () => { +xcontext('Create a workflow', () => { before(() => { + cy.server() cy.visit('localhost:4001/_builder') cy.createApp('Workflow Test App', 'This app is used to test that workflows do in fact work!') @@ -8,12 +9,10 @@ context('Create a workflow', () => { // https://on.cypress.io/interacting-with-elements it('should create a workflow', () => { - cy.createModel('dog', 'name', 'age') - cy.createUser('bbuser', 'test', 'ADMIN') - + cy.createTable('dog', 'name', 'age') cy.contains('workflow').click() - cy.get('.new-workflow-button').click() + cy.contains('Create New Workflow').click() cy.get('input').type('Add Record') cy.contains('Save').click() @@ -28,14 +27,13 @@ context('Create a workflow', () => { cy.get(':nth-child(3) > .budibase__input').type('11') // Save - cy.get('[data-cy=save-workflow-setup]').click() - cy.get('.workflow-button').click() + cy.contains('Save Workflow').click() // Activate Workflow cy.get('[data-cy=activate-workflow]').click() }) - it('should add record when a new record is added', () => { + xit('should add record when a new record is added', () => { cy.contains('backend').click() cy.addRecord('bob', '15') diff --git a/packages/builder/cypress/integration/screens.spec.js b/packages/builder/cypress/integration/screens.spec.js index 43f7d4491..65879b7b5 100644 --- a/packages/builder/cypress/integration/screens.spec.js +++ b/packages/builder/cypress/integration/screens.spec.js @@ -1,13 +1,14 @@ context('Screen Tests', () => { before(() => { + cy.server() cy.visit('localhost:4001/_builder') cy.createApp('Conor Cy App', 'Model App Description') cy.navigateToFrontend() }) it('Should successful create a screen', () => { - cy.createScreen("test Screen") + cy.createScreen("test Screen", "/test") }) it('Should rename a screen', () => { diff --git a/packages/builder/cypress/plugins/index.js b/packages/builder/cypress/plugins/index.js index 59b2bab6e..771ba886b 100644 --- a/packages/builder/cypress/plugins/index.js +++ b/packages/builder/cypress/plugins/index.js @@ -19,4 +19,5 @@ module.exports = (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config + require("cypress-terminal-report/src/installLogsPrinter")(on) } diff --git a/packages/builder/cypress/setup.js b/packages/builder/cypress/setup.js index 80516471d..1003e6e42 100644 --- a/packages/builder/cypress/setup.js +++ b/packages/builder/cypress/setup.js @@ -1,17 +1,23 @@ // What this script does: -// 1. Removes the old test folder if it exists (.budibase-cypress) -// 2. Initialises using `.budibase-cypress` +// 1. Removes the old test folder if it exists (.budibase) +// 2. Initialises using `.budibase` // 3. Runs the server using said folder const rimraf = require("rimraf") const { join } = require("path") -const homedir = join(require("os").homedir(), ".budibase-cypress") -const init = require("../../cli/src/commands/init/initHandler") const run = require("../../cli/src/commands/run/runHandler") +const initialiseBudibase = require("../../server/src/utilities/initialiseBudibase") + +const homedir = join(require("os").homedir(), ".budibase") rimraf.sync(homedir) -init({ dir: homedir, clientId: "cypress-test" }).then(() => { - delete require.cache[require.resolve("../../server/src/environment")] - run({ dir: homedir }) -}) +process.env.BUDIBASE_API_KEY = "6BE826CB-6B30-4AEC-8777-2E90464633DE" +process.env.NODE_ENV = "cypress" + +initialiseBudibase({ dir: homedir, clientId: "cypress-test" }) + .then(() => { + delete require.cache[require.resolve("../../server/src/environment")] + run({ dir: homedir }) + }) + .catch(e => console.error(e)) diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index 33ae53d6d..40ffb0f48 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -24,69 +24,89 @@ // -- This will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) -Cypress.Commands.add("createApp", (name, description) => { - cy.get(".banner-button") - .click() - .get('input[name="name"]') - .type(name) - .should("have.value", name) - - cy.get('textarea[name="description"]') - .type(description) - .should("have.value", description) - - cy.contains("Save").click() +Cypress.Commands.add("createApp", name => { + cy.contains("Create New Web App").click() + + cy.get("body") + .then($body => { + if ($body.find("input[name=apiKey]").length) { + // input was found, do something else here + cy.get("input[name=apiKey]") + .type(name) + .should("have.value", name) + cy.contains("Next").click() + } + }) + .then(() => { + cy.get("input[name=applicationName]") + .type(name) + .should("have.value", name) + + cy.contains("Next").click() + + cy.get("input[name=username]") + .click() + .type("test") + cy.get("input[name=password]") + .click() + .type("test") + cy.contains("Submit").click() + cy.contains("Create New Table", { + timeout: 10000, + }).should("be.visible") + }) }) -Cypress.Commands.add("createModel", (modelName, firstField, secondField) => { + +Cypress.Commands.add("createTable", tableName => { // Enter model name - cy.get("[data-cy=Name]") - .click() - .type(modelName) + cy.contains("Create New Table").click() + cy.get("[placeholder='Table Name']").type(tableName) // Add 'name' field - cy.get("[data-cy=add-new-model-field]").click() - cy.get("[data-cy=Name]") - .click() - .type(firstField) cy.contains("Save").click() + cy.contains(tableName).should("be.visible") +}) - // Add 'age' field - cy.get("[data-cy=add-new-model-field]").click() +Cypress.Commands.add("addColumn", (tableName, columnName, type) => { + // Select Table + cy.contains(tableName).click() + cy.contains("Create New Column").click() - cy.get("[data-cy=Name]") - .click() - .type(secondField) - cy.get("select").select("number") - cy.contains("Save").click() - cy.contains(secondField).should("exist") + cy.get("[placeholder=Name]").type(columnName) + cy.get("select").select(type) + + cy.contains("Save Column") - // Save model cy.contains("Save").click() }) -Cypress.Commands.add("addRecord", (firstField, secondField) => { - cy.contains("Create new record").click() - cy.get("[data-cy=name-input]") - .click() - .type(firstField) - cy.get("[data-cy=age-input]") - .click() - .type(secondField) +Cypress.Commands.add("addRecord", values => { + cy.contains("Create New Row").click() + + for (let i = 0; i < values.length; i++) { + cy.get("input") + .eq(i) + .type(values[i]) + } // Save cy.contains("Save").click() }) -Cypress.Commands.add("createUser", (username, password, level) => { +Cypress.Commands.add("createUser", (username, password) => { // Create User - cy.get(".nav-group-header > .ri-add-line").click() + cy.get(".toprightnav > .settings").click() + cy.contains("Users").click() - cy.get("[data-cy=username]").type(username) - cy.get("[data-cy=password]").type(password) - cy.get("[data-cy=accessLevel]").select(level) + cy.get("[name=Name]") + .first() + .type(username) + cy.get("[name=Password]") + .first() + .type(password) // Save - cy.contains("Save").click() + cy.get(".create-button").click() }) Cypress.Commands.add("addHeadlineComponent", text => { @@ -95,7 +115,8 @@ Cypress.Commands.add("addHeadlineComponent", text => { cy.get("[data-cy=Text]").click() cy.get("[data-cy=Headline]").click() cy.get(".tabs > :nth-child(2)").click() - cy.get('input[type="text"]').type(text) + cy.contains("Settings").click() + cy.get('input[name="text"]').type(text) cy.contains("Design").click() }) Cypress.Commands.add("addButtonComponent", () => { @@ -105,9 +126,7 @@ Cypress.Commands.add("addButtonComponent", () => { }) Cypress.Commands.add("navigateToFrontend", () => { - cy.get(".close", { timeout: 10000 }).click() cy.contains("frontend").click() - cy.get(".close", { timeout: 10000 }).click() }) Cypress.Commands.add("createScreen", (screenName, route) => { diff --git a/packages/builder/cypress/support/index.js b/packages/builder/cypress/support/index.js index 15c9d759f..03ef2249f 100644 --- a/packages/builder/cypress/support/index.js +++ b/packages/builder/cypress/support/index.js @@ -19,3 +19,4 @@ import "./commands" // Alternatively you can use CommonJS syntax: // require('./commands') +require("cypress-terminal-report/src/installLogsCollector")() diff --git a/packages/builder/package.json b/packages/builder/package.json index 55903d71b..75faa8671 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.1.13", + "version": "0.1.17", "license": "AGPL-3.0", "private": true, "scripts": { @@ -13,7 +13,9 @@ "cy:setup": "node ./cypress/setup.js", "cy:run": "cypress run", "cy:open": "cypress open", - "cy:test": "start-server-and-test cy:setup http://localhost:4001/_builder cy:run" + "cy:run:ci": "cypress run --browser electron --record --key f308590b-6070-41af-b970-794a3823d451", + "cy:test": "start-server-and-test cy:setup http://localhost:4001/_builder cy:run", + "cy:ci": "start-server-and-test cy:setup http://localhost:4001/_builder cy:run:ci" }, "jest": { "globals": { @@ -55,23 +57,24 @@ ] }, "dependencies": { - "@budibase/bbui": "^1.18.0", - "@budibase/client": "^0.1.1", + "@budibase/bbui": "^1.23.1", + "@budibase/client": "^0.1.17", "@budibase/colorpicker": "^1.0.1", "@nx-js/compiler-util": "^2.0.0", - "britecharts": "^2.16.0", "@sentry/browser": "5.19.1", "@svelteschool/svelte-forms": "^0.7.0", + "britecharts": "^2.16.0", "codemirror": "^5.51.0", "d3-selection": "^1.4.1", "date-fns": "^1.29.0", "deepmerge": "^4.2.2", + "fast-sort": "^2.2.0", "feather-icons": "^4.21.0", "flatpickr": "^4.5.7", "lodash": "^4.17.13", "lunr": "^2.3.5", "mustache": "^4.0.1", - "posthog-js": "^1.3.1", + "posthog-js": "1.3.1", "safe-buffer": "^5.1.2", "shortid": "^2.2.15", "string_decoder": "^1.2.0", @@ -93,8 +96,10 @@ "babel-jest": "^24.8.0", "browser-sync": "^2.26.7", "cypress": "^4.8.0", + "cypress-terminal-report": "^1.4.1", "eslint-plugin-cypress": "^2.11.1", "http-proxy-middleware": "^0.19.1", + "identity-obj-proxy": "^3.0.0", "jest": "^24.8.0", "ncp": "^2.0.0", "npm-run-all": "^4.1.5", @@ -116,4 +121,4 @@ "svelte-jester": "^1.0.6" }, "gitHead": "115189f72a850bfb52b65ec61d932531bf327072" -} \ No newline at end of file +} diff --git a/packages/builder/src/analytics.js b/packages/builder/src/analytics.js index 092a6f630..43b51eb5f 100644 --- a/packages/builder/src/analytics.js +++ b/packages/builder/src/analytics.js @@ -3,6 +3,7 @@ import posthog from "posthog-js" function activate() { Sentry.init({ dsn: process.env.SENTRY_DSN }) + if (!process.env.POSTHOG_TOKEN) return posthog.init(process.env.POSTHOG_TOKEN, { api_host: process.env.POSTHOG_URL, }) @@ -13,7 +14,7 @@ function captureException(err) { } function captureEvent(event) { - if (process.env.NODE_ENV !== "production") return + if (!process.env.POSTHOG_TOKEN) return posthog.capture(event) } diff --git a/packages/builder/src/builderStore/store/backend.js b/packages/builder/src/builderStore/store/backend.js index 393371ce8..28272d989 100644 --- a/packages/builder/src/builderStore/store/backend.js +++ b/packages/builder/src/builderStore/store/backend.js @@ -61,10 +61,9 @@ export const getBackendUiStore = () => { state.draftModel = cloneDeep(model) state.selectedField = "" state.selectedView = `all_${model._id}` - state.tabs.SETUP_PANEL = "SETUP" return state }), - save: async ({ model }) => { + save: async model => { const updatedModel = cloneDeep(model) // update any renamed schema keys to reflect their names @@ -83,20 +82,35 @@ export const getBackendUiStore = () => { const savedModel = await response.json() await store.actions.models.fetch() store.actions.models.select(savedModel) + return savedModel }, - addField: field => { + delete: async model => { + await api.delete(`/api/models/${model._id}/${model._rev}`) store.update(state => { - if (!state.draftModel.schema) { - state.draftModel.schema = {} - } + state.models = state.models.filter( + existing => existing._id !== model._id + ) + state.selectedModel = state.models[0] || {} + return state + }) + }, + saveField: ({ originalName, field }) => { + store.update(state => { + // delete the original if renaming + delete state.draftModel.schema[originalName] state.draftModel.schema = { ...state.draftModel.schema, [field.name]: cloneDeep(field), } - state.selectedField = field.name - state.tabs.NAVIGATION_PANEL = "NAVIGATE" - + store.actions.models.save(state.draftModel) + return state + }) + }, + deleteField: field => { + store.update(state => { + delete state.draftModel.schema[field.name] + store.actions.models.save(state.draftModel) return state }) }, diff --git a/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte b/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte index bbd9d6d4a..fa36018ae 100644 --- a/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte +++ b/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte @@ -1,38 +1,19 @@ - -
| Edit | -Name | -Type | -- |
|---|---|---|---|
| - editField(meta)} /> - | -
- {key}
- |
- {meta.type} | -- deleteField(meta)} /> - | -
Before you can view your table, you need to set it up.
-Blocks are pre-made fields and help you build your table quicker.
-Blocks are pre-made fields and help you build your table quicker.
-Blocks are pre-made fields and help you build your table quicker.
-