cypress + unit test framework

This commit is contained in:
Martin McKeaveney 2020-08-10 15:34:37 +01:00
parent cfe262d602
commit cf084f2877
14 changed files with 296 additions and 105 deletions

View File

@ -5,7 +5,7 @@ xcontext('Create Components', () => {
cy.visit('localhost:4001/_builder') cy.visit('localhost:4001/_builder')
// https://on.cypress.io/type // https://on.cypress.io/type
cy.createApp('Model App', 'Model App Description') cy.createApp('Model App', 'Model App Description')
cy.createModel('dog', 'name', 'age') cy.createTable('dog', 'name', 'age')
cy.addRecord('bob', '15') cy.addRecord('bob', '15')
}) })

View File

@ -1,42 +1,41 @@
xcontext('Create a Table', () => { context('Create a Table', () => {
before(() => { before(() => {
cy.visit('localhost:4001/_builder') cy.visit('localhost:4001/_builder')
// https://on.cypress.io/type
cy.createApp('Table App', 'Table App Description') cy.createApp('Table App', 'Table App Description')
}) })
// https://on.cypress.io/interacting-with-elements // https://on.cypress.io/interacting-with-elements
it('should create a new Table', () => { it('should create a new Table', () => {
cy.createTable('dog', 'name', 'age') cy.createTable('dog')
// Check if Table exists // Check if Table exists
cy.get('.title').should('have.text', 'dog') cy.get('.title').should('have.text', 'dog')
}) })
it('adds a new column to the table', () => { // it('adds a new column to the table', () => {
cy.addRecord('bob', '15') // cy.addRecord('bob', '15')
cy.contains('bob').should('have.text', 'bob') // cy.contains('bob').should('have.text', 'bob')
}) // })
it('updates a column on the table', () => { // it('updates a column on the table', () => {
cy.addRecord('bob', '15') // cy.addRecord('bob', '15')
cy.contains('bob').should('have.text', 'bob') // cy.contains('bob').should('have.text', 'bob')
}) // })
it('edits a record', () => { // it('edits a record', () => {
cy.addRecord('bob', '15') // cy.addRecord('bob', '15')
cy.contains('bob').should('have.text', 'bob') // cy.contains('bob').should('have.text', 'bob')
}) // })
it('deletes a record', () => { // it('deletes a record', () => {
cy.addRecord('bob', '15') // cy.addRecord('bob', '15')
cy.contains('bob').should('have.text', 'bob') // cy.contains('bob').should('have.text', 'bob')
}) // })
}) })

View File

@ -9,7 +9,7 @@ xcontext('Create a workflow', () => {
// https://on.cypress.io/interacting-with-elements // https://on.cypress.io/interacting-with-elements
it('should create a workflow', () => { it('should create a workflow', () => {
cy.createModel('dog', 'name', 'age') cy.createTable('dog', 'name', 'age')
cy.contains('workflow').click() cy.contains('workflow').click()
cy.contains('Create New Workflow').click() cy.contains('Create New Workflow').click()

View File

@ -57,22 +57,28 @@ Cypress.Commands.add("createApp", name => {
}) })
}) })
Cypress.Commands.add("createModel", modelName => { Cypress.Commands.add("createTable", tableName => {
// Enter model name // Enter model name
cy.contains("Create New Table").click() cy.contains("Create New Table").click()
cy.get("[data-cy=table-name-input]").type(modelName) cy.get("[placeholder='Table Name']").type(tableName)
// Add 'name' field // Add 'name' field
cy.contains("Add").click()
cy.contains("Plain Text").click()
// Add 'age' field
cy.contains("Add").click()
cy.contains("Number").click()
cy.contains("Save").click() cy.contains("Save").click()
cy.contains(tableName).should("be.visible")
})
cy.contains(modelName).click() Cypress.Commands.add("addColumn", (tableName, columnName, type) => {
// Select Table
cy.contains(tableName).click()
// Click "Create New Column"
// Fill in dropdown
//hit save
// assertions
// Add 'name' field
cy.contains("Save").click()
cy.contains(modelName).should("be.visible").click()
}) })
Cypress.Commands.add("addRecord", (firstField, secondField) => { Cypress.Commands.add("addRecord", (firstField, secondField) => {

View File

@ -83,13 +83,16 @@ export const getBackendUiStore = () => {
await store.actions.models.fetch() await store.actions.models.fetch()
store.actions.models.select(savedModel) store.actions.models.select(savedModel)
}, },
delete: async model => {
await api.delete(`/api/models/${model._id}/${model._rev}`)
store.update(state => {
state.selectedModel = state.models[0] || {}
state.models = state.models.filter(existing => existing._id !== model._id)
return state
})
},
saveField: ({ originalName, field }) => { saveField: ({ originalName, field }) => {
store.update(state => { store.update(state => {
// TODO: is this necessary?
// if (!state.draftModel.schema) {
// state.draftModel.schema = {}
// }
// delete the original if renaming // delete the original if renaming
delete state.draftModel.schema[originalName] delete state.draftModel.schema[originalName]

View File

@ -1,6 +1,6 @@
<script> <script>
import { onMount } from "svelte" import { onMount } from "svelte"
import fsort from "fast-sort"; import fsort from "fast-sort"
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import { Button, Icon } from "@budibase/bbui" import { Button, Icon } from "@budibase/bbui"
import Select from "components/common/Select.svelte" import Select from "components/common/Select.svelte"
@ -71,13 +71,15 @@
<h2 class="title">{$backendUiStore.selectedModel.name}</h2> <h2 class="title">{$backendUiStore.selectedModel.name}</h2>
<div class="popovers"> <div class="popovers">
<ColumnPopover /> <ColumnPopover />
{#if Object.keys($backendUiStore.selectedModel.schema).length > 0}
<RowPopover /> <RowPopover />
{/if}
</div> </div>
</div> </div>
<table class="uk-table"> <table class="uk-table">
<thead> <thead>
<tr> <tr>
<th> <th class="edit-header">
<div>Edit</div> <div>Edit</div>
</th> </th>
{#each headers as header} {#each headers as header}
@ -146,16 +148,21 @@
font-size: 14px; font-size: 14px;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
transition: 0.5s all; transition: 0.5s all;
vertical-align: middle;
} }
/* thead th div { .edit-header {
display: flex; width: 100px;
justify-content: space-between; cursor: default;
align-items: center; }
} */
.edit-header:hover {
color: var(--ink);
}
th:hover { th:hover {
color: var(--blue); color: var(--blue);
cursor: pointer;
} }
td { td {
@ -183,36 +190,7 @@
display: flex; display: flex;
} }
.ri-more-line:hover,
.uk-dropdown-nav li:hover {
cursor: pointer;
}
.no-data { .no-data {
padding: 20px; padding: 14px;
}
li {
display: flex;
align-items: center;
border-radius: 5px;
}
i {
color: var(--grey-7);
margin-right: 8px;
font-size: 20px;
}
.label {
color: var(--grey-7);
font-size: 14px;
font-family: inter;
font-weight: 400;
margin: 12px 0px;
}
.label:hover {
color: var(--ink);
cursor: pointer;
} }
</style> </style>

View File

@ -18,9 +18,8 @@
export let onClosed export let onClosed
export let field = {} export let field = {}
export let columnName
let originalName = columnName let originalName = field.name
$: required = $: required =
field.constraints && field.constraints &&
@ -36,10 +35,7 @@
backendUiStore.update(state => { backendUiStore.update(state => {
backendUiStore.actions.models.saveField({ backendUiStore.actions.models.saveField({
originalName, originalName,
field: { field
...field,
name: columnName
}
}) })
return state return state
@ -49,7 +45,7 @@
</script> </script>
<div class="actions"> <div class="actions">
<Input placeholder="Name" thin bind:value={columnName} /> <Input placeholder="Name" thin bind:value={field.name} />
<Select secondary thin bind:value={field.type}> <Select secondary thin bind:value={field.type}>
{#each Object.values(FIELDS) as field} {#each Object.values(FIELDS) as field}

View File

@ -0,0 +1,64 @@
<script>
import ActionButton from "components/common/ActionButton.svelte"
import { notifier } from "builderStore/store/notifications"
import { store, backendUiStore } from "builderStore"
import * as api from "../api"
export let table
export let onClosed
function deleteTable() {
backendUiStore.actions.models.delete(table)
}
</script>
<section>
<div class="content">
<heading>
<i class="ri-information-line alert" />
<h4 class="budibase__title--4">Delete Table</h4>
</heading>
<p>
Are you sure you want to delete this table? All of your data will be
permanently removed. This action cannot be undone.
</p>
</div>
<div class="modal-actions">
<ActionButton on:click={onClosed}>Cancel</ActionButton>
<ActionButton
alert
on:click={async () => {
await backendUiStore.actions.models.delete(table)
notifier.danger('Table deleted')
onClosed()
}}>
Delete
</ActionButton>
</div>
</section>
<style>
.alert {
color: rgba(255, 0, 31, 1);
background: var(--grey-1);
padding: 5px;
}
.modal-actions {
padding: 10px;
background: var(--grey-1);
border-top: 1px solid #ccc;
}
heading {
display: flex;
align-items: center;
}
.content {
padding: 30px;
}
h4 {
margin: 0 0 0 10px;
}
</style>

View File

@ -13,6 +13,9 @@
let editing let editing
$: sortColumn = $backendUiStore.sort && $backendUiStore.sort.column
$: sortDirection = $backendUiStore.sort && $backendUiStore.sort.direction
function showEditor() { function showEditor() {
editing = true editing = true
} }
@ -44,8 +47,8 @@
<h4>Edit Column</h4> <h4>Edit Column</h4>
<CreateEditColumn <CreateEditColumn
onClosed={hideEditor} onClosed={hideEditor}
field={field.field} field={field}
columnName={field.name} /> />
{:else} {:else}
<ul> <ul>
<li on:click={showEditor}> <li on:click={showEditor}>
@ -56,14 +59,18 @@
<Icon name="delete" /> <Icon name="delete" />
Delete Delete
</li> </li>
{#if sortDirection === 'desc' || sortColumn !== field.name}
<li on:click={() => sort('asc', field.name)}> <li on:click={() => sort('asc', field.name)}>
<Icon name="sortascending" /> <Icon name="sortascending" />
Sort A - Z Sort A - Z
</li> </li>
{/if}
{#if sortDirection === 'asc' || sortColumn !== field.name}
<li on:click={() => sort('desc', field.name)}> <li on:click={() => sort('desc', field.name)}>
<Icon name="sortdescending" /> <Icon name="sortdescending" />
Sort Z - A Sort Z - A
</li> </li>
{/if}
</ul> </ul>
{/if} {/if}
</DropdownMenu> </DropdownMenu>

View File

@ -25,7 +25,10 @@
<DropdownMenu bind:this={dropdown} {anchor} align="left"> <DropdownMenu bind:this={dropdown} {anchor} align="left">
<div class="container"> <div class="container">
<h4>Create Table</h4> <h4>Create Table</h4>
<Input placeholder="Table Name" thin bind:value={name} /> <Input
placeholder="Table Name"
thin
bind:value={name} />
</div> </div>
<footer> <footer>
<div class="button-margin-3"> <div class="button-margin-3">

View File

@ -0,0 +1,133 @@
<script>
import { getContext } from "svelte"
import { backendUiStore } from "builderStore"
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
import { FIELDS } from "constants/backend"
import DeleteTableModal from "components/database/ModelDataTable/modals/DeleteTable.svelte"
const { open, close } = getContext("simple-modal")
export let table
let anchor
let dropdown
let editing
function showEditor() {
editing = true
}
function hideEditor() {
dropdown.hide()
editing = false
close()
}
const deleteTable = () => {
open(
DeleteTableModal,
{
onClosed: close,
table,
},
{ styleContent: { padding: "0" } }
)
}
function save() {}
</script>
<div bind:this={anchor} on:click={dropdown.show}>
<i class="ri-more-line" />
</div>
<DropdownMenu bind:this={dropdown} {anchor} align="left">
{#if editing}
<div class="container">
<h4>Edit Table</h4>
<Input placeholder="Table Name" thin bind:value={table.name} />
</div>
<footer>
<div class="button-margin-3">
<Button secondary on:click={hideEditor}>Cancel</Button>
</div>
<div class="button-margin-4">
<Button primary on:click={save}>Save</Button>
</div>
</footer>
{:else}
<ul>
<li on:click={showEditor}>
<Icon name="edit" />
Edit
</li>
<li on:click={deleteTable}>
<Icon name="delete" />
Delete
</li>
</ul>
{/if}
</DropdownMenu>
<style>
h4 {
padding: var(--spacing-l);
margin: 0;
}
ul {
list-style: none;
padding-left: 0;
margin: 0;
padding: var(--spacing-s) 0;
}
li {
display: flex;
font-family: var(--font-sans);
font-size: var(--font-size-xs);
color: var(--ink);
padding: var(--spacing-s) var(--spacing-m);
margin: auto 0px;
align-items: center;
cursor: pointer;
}
li:hover {
background-color: var(--grey-2);
}
li:active {
color: var(--blue);
}
.container {
padding: var(--spacing-l);
margin: 0;
}
footer {
padding: 20px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 20px;
background: var(--grey-1);
border-bottom-left-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
}
.button-margin-1 {
grid-column-start: 1;
display: grid;
}
.button-margin-3 {
grid-column-start: 3;
display: grid;
}
.button-margin-4 {
grid-column-start: 4;
display: grid;
}
</style>

View File

@ -21,7 +21,8 @@
padding: 0 10px 0 10px; padding: 0 10px 0 10px;
height: 36px; height: 36px;
border-radius: 5px; border-radius: 5px;
display: flex; display: grid;
grid-template-columns: 30px 1fr 20px;
align-items: center; align-items: center;
transition: 0.3s background-color; transition: 0.3s background-color;
color: var(--ink); color: var(--ink);

View File

@ -6,7 +6,8 @@
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import ListItem from "./ListItem.svelte" import ListItem from "./ListItem.svelte"
import { Button } from "@budibase/bbui" import { Button } from "@budibase/bbui"
import CreateTablePopover from "./CreateEditTable.svelte" import CreateTablePopover from "./CreateTable.svelte"
import EditTablePopover from "./EditTable.svelte"
const { open, close } = getContext("simple-modal") const { open, close } = getContext("simple-modal")
@ -52,7 +53,7 @@
title={model.name} title={model.name}
icon="ri-table-fill" icon="ri-table-fill"
on:click={() => selectModel(model)}> on:click={() => selectModel(model)}>
<EditTablePopover table={model} />
</ListItem> </ListItem>
{/each} {/each}
</div> </div>

View File

@ -65,15 +65,15 @@ export const FIELDS = {
// presence: { allowEmpty: true }, // presence: { allowEmpty: true },
// }, // },
// }, // },
LINKED_FIELDS: { // LINKED_FIELDS: {
name: "Linked Fields", // name: "Linked Fields",
icon: "ri-link", // icon: "ri-link",
type: "link", // type: "link",
modelId: null, // modelId: null,
constraints: { // constraints: {
type: "array", // type: "array",
}, // },
}, // },
} }
export const BLOCKS = { export const BLOCKS = {