1
0
Fork 0
mirror of synced 2024-07-15 11:15:59 +12:00

Merge branch 'develop' into chore/vscode_setup

This commit is contained in:
adrinr 2023-02-03 11:50:19 +00:00
commit 9467c4dd4d
176 changed files with 2170 additions and 7697 deletions

View file

@ -194,5 +194,5 @@ jobs:
PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }}
with:
repository: budibase/budibase-deploys
event: deploy-develop-to-qa
event: deploy-budibase-develop-to-qa
github_pat: ${{ secrets.GH_ACCESS_TOKEN }}

View file

@ -18,30 +18,18 @@ jobs:
- run: yarn
- run: yarn bootstrap
- run: yarn build
- name: Pull cypress.env.yaml from budibase-infra
- name: Pull from budibase-infra
run: |
curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \
-H 'Accept: application/vnd.github.v3.raw' \
-o packages/builder/cypress.env.json \
-L https://api.github.com/repos/budibase/budibase-infra/contents/test/cypress.env.json
wc -l packages/builder/cypress.env.json
- name: Cypress run
id: cypress
continue-on-error: true
uses: cypress-io/github-action@v2
with:
record: true
install: false
tag: nightly
command: yarn test:e2e:ci:record
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
-o
-L
wc -l
- uses: actions/upload-artifact@v3
with:
name: Test Reports
path: packages/builder/cypress/reports/testReport.html
path:
# TODO: enable once running in QA test env
# - name: Configure AWS Credentials
@ -54,11 +42,3 @@ jobs:
# - name: Upload test results HTML
# uses: aws-actions/configure-aws-credentials@v1
# run: aws s3 cp packages/builder/cypress/reports/testReport.html s3://{{ secrets.BUDI_QA_REPORTS_BUCKET_NAME }}/$GITHUB_RUN_ID/index.html
- name: Cypress Discord Notify
run: yarn test:e2e:ci:notify
env:
CYPRESS_WEBHOOK_URL: ${{ secrets.BUDI_QA_WEBHOOK }}
CYPRESS_OUTCOME: ${{ steps.cypress.outcome }}
CYPRESS_DASHBOARD_URL: ${{ steps.cypress.outputs.dashboardUrl }}
GITHUB_RUN_URL: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID

View file

@ -76,7 +76,7 @@ affinity: {}
globals:
appVersion: "latest"
budibaseEnv: PRODUCTION
tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS"
tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR"
enableAnalytics: "1"
sentryDSN: ""
posthogToken: "phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU"

View file

@ -10,7 +10,7 @@ declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONME
[[ -z "${MINIO_URL}" ]] && export MINIO_URL=http://localhost:9000
[[ -z "${NODE_ENV}" ]] && export NODE_ENV=production
[[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS"
[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR"
[[ -z "${ACCOUNT_PORTAL_URL}" ]] && export ACCOUNT_PORTAL_URL=https://account.budibase.app
[[ -z "${REDIS_URL}" ]] && export REDIS_URL=localhost:6379
[[ -z "${SELF_HOSTED}" ]] && export SELF_HOSTED=1

View file

@ -1,5 +1,5 @@
{
"version": "2.2.12-alpha.54",
"version": "2.2.12-alpha.69",
"npmClient": "yarn",
"packages": [
"packages/*"

View file

@ -51,10 +51,6 @@
"lint:fix:eslint": "eslint --fix packages qa-core",
"lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --write \"qa-core/**/*.{js,ts,svelte}\"",
"lint:fix": "yarn run lint:fix:prettier && yarn run lint:fix:eslint",
"test:e2e": "lerna run cy:test --stream",
"test:e2e:ci": "lerna run cy:ci --stream",
"test:e2e:ci:record": "lerna run cy:ci:record --stream",
"test:e2e:ci:notify": "lerna run cy:ci:notify",
"build:specs": "lerna run specs",
"build:docker": "lerna run build:docker && npm run build:docker:proxy && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -",
"build:docker:pre": "lerna run build && lerna run predocker",
@ -84,4 +80,4 @@
"install:pro": "bash scripts/pro/install.sh",
"dep:clean": "yarn clean && yarn bootstrap"
}
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@budibase/backend-core",
"version": "2.2.12-alpha.54",
"version": "2.2.12-alpha.69",
"description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
@ -23,7 +23,7 @@
},
"dependencies": {
"@budibase/nano": "10.1.1",
"@budibase/types": "2.2.12-alpha.54",
"@budibase/types": "2.2.12-alpha.69",
"@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2",
"aws-cloudfront-sign": "2.2.0",

View file

@ -15,18 +15,47 @@ import { getCouchInfo } from "./connections"
import { directCouchCall } from "./utils"
import { getPouchDB } from "./pouchDB"
import { WriteStream, ReadStream } from "fs"
import { newid } from "../../newid"
function buildNano(couchInfo: { url: string; cookie: string }) {
return Nano({
url: couchInfo.url,
requestDefaults: {
headers: {
Authorization: couchInfo.cookie,
},
},
parseUrl: false,
})
}
export function DatabaseWithConnection(
dbName: string,
connection: string,
opts?: DatabaseOpts
) {
if (!connection) {
throw new Error("Must provide connection details")
}
return new DatabaseImpl(dbName, opts, connection)
}
export class DatabaseImpl implements Database {
public readonly name: string
private static nano: Nano.ServerScope
private readonly instanceNano?: Nano.ServerScope
private readonly pouchOpts: DatabaseOpts
constructor(dbName?: string, opts?: DatabaseOpts) {
constructor(dbName?: string, opts?: DatabaseOpts, connection?: string) {
if (dbName == null) {
throw new Error("Database name cannot be undefined.")
}
this.name = dbName
this.pouchOpts = opts || {}
if (connection) {
const couchInfo = getCouchInfo(connection)
this.instanceNano = buildNano(couchInfo)
}
if (!DatabaseImpl.nano) {
DatabaseImpl.init()
}
@ -34,15 +63,7 @@ export class DatabaseImpl implements Database {
static init() {
const couchInfo = getCouchInfo()
DatabaseImpl.nano = Nano({
url: couchInfo.url,
requestDefaults: {
headers: {
Authorization: couchInfo.cookie,
},
},
parseUrl: false,
})
DatabaseImpl.nano = buildNano(couchInfo)
}
async exists() {
@ -50,6 +71,10 @@ export class DatabaseImpl implements Database {
return response.status === 200
}
private nano() {
return this.instanceNano || DatabaseImpl.nano
}
async checkSetup() {
let shouldCreate = !this.pouchOpts?.skip_setup
// check exists in a lightweight fashion
@ -58,9 +83,9 @@ export class DatabaseImpl implements Database {
throw new Error("DB does not exist")
}
if (!exists) {
await DatabaseImpl.nano.db.create(this.name)
await this.nano().db.create(this.name)
}
return DatabaseImpl.nano.db.use(this.name)
return this.nano().db.use(this.name)
}
private async updateOutput(fnc: any) {
@ -101,6 +126,13 @@ export class DatabaseImpl implements Database {
return this.updateOutput(() => db.destroy(_id, _rev))
}
async post(document: AnyDocument, opts?: DatabasePutOpts) {
if (!document._id) {
document._id = newid()
}
return this.put(document, opts)
}
async put(document: AnyDocument, opts?: DatabasePutOpts) {
if (!document._id) {
throw new Error("Cannot store document without _id field.")
@ -146,7 +178,7 @@ export class DatabaseImpl implements Database {
async destroy() {
try {
await DatabaseImpl.nano.db.destroy(this.name)
await this.nano().db.destroy(this.name)
} catch (err: any) {
// didn't exist, don't worry
if (err.statusCode === 404) {

View file

@ -1,7 +1,7 @@
import env from "../../environment"
export const getCouchInfo = () => {
const urlInfo = getUrlInfo()
export const getCouchInfo = (connection?: string) => {
const urlInfo = getUrlInfo(connection)
let username
let password
if (env.COUCH_DB_USERNAME) {

View file

@ -6,7 +6,7 @@ import * as tenancy from "../tenancy"
* The env var is formatted as:
* tenant1:feature1:feature2,tenant2:feature1
*/
function getFeatureFlags() {
export function buildFeatureFlags() {
if (!env.TENANT_FEATURE_FLAGS) {
return
}
@ -27,8 +27,6 @@ function getFeatureFlags() {
return tenantFeatureFlags
}
const TENANT_FEATURE_FLAGS = getFeatureFlags()
export function isEnabled(featureFlag: string) {
const tenantId = tenancy.getTenantId()
const flags = getTenantFeatureFlags(tenantId)
@ -36,18 +34,36 @@ export function isEnabled(featureFlag: string) {
}
export function getTenantFeatureFlags(tenantId: string) {
const flags = []
let flags: string[] = []
const envFlags = buildFeatureFlags()
if (envFlags) {
const globalFlags = envFlags["*"]
const tenantFlags = envFlags[tenantId] || []
if (TENANT_FEATURE_FLAGS) {
const globalFlags = TENANT_FEATURE_FLAGS["*"]
const tenantFlags = TENANT_FEATURE_FLAGS[tenantId]
// Explicitly exclude tenants from global features if required.
// Prefix the tenant flag with '!'
const tenantOverrides = tenantFlags.reduce(
(acc: string[], flag: string) => {
if (flag.startsWith("!")) {
let stripped = flag.substring(1)
acc.push(stripped)
}
return acc
},
[]
)
if (globalFlags) {
flags.push(...globalFlags)
}
if (tenantFlags) {
if (tenantFlags.length) {
flags.push(...tenantFlags)
}
// Purge any tenant specific overrides
flags = flags.filter(flag => {
return tenantOverrides.indexOf(flag) == -1 && !flag.startsWith("!")
})
}
return flags
@ -57,4 +73,5 @@ export enum TenantFeatureFlag {
LICENSING = "LICENSING",
GOOGLE_SHEETS = "GOOGLE_SHEETS",
USER_GROUPS = "USER_GROUPS",
ONBOARDING_TOUR = "ONBOARDING_TOUR",
}

View file

@ -0,0 +1,85 @@
import {
TenantFeatureFlag,
buildFeatureFlags,
getTenantFeatureFlags,
} from "../"
import env from "../../environment"
const { ONBOARDING_TOUR, LICENSING, USER_GROUPS } = TenantFeatureFlag
describe("featureFlags", () => {
beforeEach(() => {
env._set("TENANT_FEATURE_FLAGS", "")
})
it("Should return no flags when the TENANT_FEATURE_FLAG is empty", async () => {
let features = buildFeatureFlags()
expect(features).toBeUndefined()
})
it("Should generate a map of global and named tenant feature flags from the env value", async () => {
env._set(
"TENANT_FEATURE_FLAGS",
`*:${ONBOARDING_TOUR},tenant1:!${ONBOARDING_TOUR},tenant2:${USER_GROUPS},tenant1:${LICENSING}`
)
const parsedFlags: Record<string, string[]> = {
"*": [ONBOARDING_TOUR],
tenant1: [`!${ONBOARDING_TOUR}`, LICENSING],
tenant2: [USER_GROUPS],
}
let features = buildFeatureFlags()
expect(features).toBeDefined()
expect(features).toEqual(parsedFlags)
})
it("Should add feature flag flag only to explicitly configured tenant", async () => {
env._set(
"TENANT_FEATURE_FLAGS",
`*:${LICENSING},*:${USER_GROUPS},tenant1:${ONBOARDING_TOUR}`
)
let tenant1Flags = getTenantFeatureFlags("tenant1")
let tenant2Flags = getTenantFeatureFlags("tenant2")
expect(tenant1Flags).toBeDefined()
expect(tenant1Flags).toEqual([LICENSING, USER_GROUPS, ONBOARDING_TOUR])
expect(tenant2Flags).toBeDefined()
expect(tenant2Flags).toEqual([LICENSING, USER_GROUPS])
})
})
it("Should exclude tenant1 from global feature flag", async () => {
env._set(
"TENANT_FEATURE_FLAGS",
`*:${LICENSING},*:${ONBOARDING_TOUR},tenant1:!${ONBOARDING_TOUR}`
)
let tenant1Flags = getTenantFeatureFlags("tenant1")
let tenant2Flags = getTenantFeatureFlags("tenant2")
expect(tenant1Flags).toBeDefined()
expect(tenant1Flags).toEqual([LICENSING])
expect(tenant2Flags).toBeDefined()
expect(tenant2Flags).toEqual([LICENSING, ONBOARDING_TOUR])
})
it("Should explicitly add flags to configured tenants only", async () => {
env._set(
"TENANT_FEATURE_FLAGS",
`tenant1:${ONBOARDING_TOUR},tenant1:${LICENSING},tenant2:${LICENSING}`
)
let tenant1Flags = getTenantFeatureFlags("tenant1")
let tenant2Flags = getTenantFeatureFlags("tenant2")
expect(tenant1Flags).toBeDefined()
expect(tenant1Flags).toEqual([ONBOARDING_TOUR, LICENSING])
expect(tenant2Flags).toBeDefined()
expect(tenant2Flags).toEqual([LICENSING])
})

View file

@ -310,6 +310,11 @@
qs "^6.11.0"
tough-cookie "^4.1.2"
"@budibase/types@2.2.12-alpha.62":
version "2.2.12-alpha.62"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.62.tgz#385ef000610d5c00b83cb2eafda2bd63c86b7f3f"
integrity sha512-idlhB4fSyBCEDWsVvQvdmN9Dg9VAEwxZ8TLE9pGnXIRZPg48MKXPNn5AUT9zv6cDlbQdlU2tFFF8st9b6lyLuw==
"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
@ -2773,9 +2778,9 @@ http-assert@^1.3.0:
http-errors "~1.8.0"
http-cache-semantics@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
version "4.1.1"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
http-cookie-agent@^4.0.2:
version "4.0.2"

View file

@ -1,7 +1,7 @@
{
"name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.",
"version": "2.2.12-alpha.54",
"version": "2.2.12-alpha.69",
"license": "MPL-2.0",
"svelte": "src/index.js",
"module": "dist/bbui.es.js",
@ -38,7 +38,7 @@
],
"dependencies": {
"@adobe/spectrum-css-workflow-icons": "1.2.1",
"@budibase/string-templates": "2.2.12-alpha.54",
"@budibase/string-templates": "2.2.12-alpha.69",
"@spectrum-css/accordion": "3.0.24",
"@spectrum-css/actionbutton": "1.0.1",
"@spectrum-css/actiongroup": "1.0.1",

View file

@ -9,7 +9,6 @@
export let longPressable = false
export let disabled = false
export let icon = ""
export let dataCy = null
export let size = "M"
export let active = false
export let fullWidth = false
@ -37,7 +36,6 @@
</script>
<button
data-cy={dataCy}
use:longPress
class:spectrum-ActionButton--quiet={quiet}
class:spectrum-ActionButton--emphasized={emphasized}
@ -88,7 +86,7 @@
}
.is-selected:not(.spectrum-ActionButton--emphasized):not(.spectrum-ActionButton--quiet) {
background: var(--spectrum-global-color-gray-300);
border-color: var(--spectrum-global-color-gray-700);
border-color: var(--spectrum-global-color-gray-500);
}
.noPadding {
padding: 0;

View file

@ -6,7 +6,6 @@
export let disabled = false
export let align = "left"
export let portalTarget
export let dataCy
let anchor
let dropdown
@ -37,7 +36,7 @@
<div use:getAnchor on:click={openMenu}>
<slot name="control" />
</div>
<Popover bind:this={dropdown} {anchor} {align} {portalTarget} {dataCy}>
<Popover bind:this={dropdown} {anchor} {align} {portalTarget}>
<Menu>
<slot />
</Menu>

View file

@ -1,11 +1,21 @@
export default function positionDropdown(
element,
{ anchor, align, maxWidth, useAnchorWidth }
) {
const update = () => {
export default function positionDropdown(element, opts) {
let resizeObserver
let latestOpts = opts
// We need a static reference to this function so that we can properly
// clean up the scroll listener.
const scrollUpdate = () => {
updatePosition(latestOpts)
}
// Updates the position of the dropdown
const updatePosition = opts => {
const { anchor, align, maxWidth, useAnchorWidth, offset = 5 } = opts
if (!anchor) {
return
}
// Compute bounds
const anchorBounds = anchor.getBoundingClientRect()
const elementBounds = element.getBoundingClientRect()
let styles = {
@ -20,9 +30,9 @@ export default function positionDropdown(
if (align === "right-outside") {
styles.top = anchorBounds.top
} else if (window.innerHeight - anchorBounds.bottom < 100) {
styles.top = anchorBounds.top - elementBounds.height - 5
styles.top = anchorBounds.top - elementBounds.height - offset
} else {
styles.top = anchorBounds.bottom + 5
styles.top = anchorBounds.bottom + offset
styles.maxHeight = window.innerHeight - anchorBounds.bottom - 20
}
@ -36,7 +46,7 @@ export default function positionDropdown(
if (align === "right") {
styles.left = anchorBounds.left + anchorBounds.width - elementBounds.width
} else if (align === "right-outside") {
styles.left = anchorBounds.right + 10
styles.left = anchorBounds.right + offset
} else {
styles.left = anchorBounds.left
}
@ -51,26 +61,47 @@ export default function positionDropdown(
})
}
// The actual svelte action callback which creates observers on the relevant
// DOM elements
const update = newOpts => {
latestOpts = newOpts
// Cleanup old state
if (resizeObserver) {
resizeObserver.disconnect()
}
// Do nothing if no anchor
const { anchor } = newOpts
if (!anchor) {
return
}
// Observe both anchor and element and resize the popover as appropriate
resizeObserver = new ResizeObserver(() => updatePosition(newOpts))
resizeObserver.observe(anchor)
resizeObserver.observe(element)
resizeObserver.observe(document.body)
}
// Apply initial styles which don't need to change
element.style.position = "absolute"
element.style.zIndex = "9999"
// Observe both anchor and element and resize the popover as appropriate
const resizeObserver = new ResizeObserver(entries => {
entries.forEach(update)
})
if (anchor) {
resizeObserver.observe(anchor)
}
resizeObserver.observe(element)
resizeObserver.observe(document.body)
// Set up a scroll listener
document.addEventListener("scroll", scrollUpdate, true)
document.addEventListener("scroll", update, true)
// Perform initial update
update(opts)
return {
update,
destroy() {
resizeObserver.disconnect()
document.removeEventListener("scroll", update, true)
// Cleanup
if (resizeObserver) {
resizeObserver.disconnect()
}
document.removeEventListener("scroll", scrollUpdate, true)
},
}
}

View file

@ -13,7 +13,6 @@
export let icon = undefined
export let active = false
export let tooltip = undefined
export let dataCy
export let newStyles = true
export let id
@ -33,7 +32,6 @@
class:disabled
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
{disabled}
data-cy={dataCy}
on:click|preventDefault
on:mouseover={() => (showTooltip = true)}
on:focus={() => (showTooltip = true)}

View file

@ -11,7 +11,6 @@
export let id = null
export let readonly = false
export let updateOnChange = true
export let dataCy
export let align
export let autofocus = false
export let variables
@ -123,7 +122,6 @@
disabled={hbsValue.length || disabled}
{readonly}
{id}
data-cy={dataCy}
value={hbsValue.length ? `{{ ${hbsValue[0]} }}` : value}
placeholder={placeholder || ""}
on:click

View file

@ -6,7 +6,6 @@
export let id = null
export let text = null
export let disabled = false
export let dataCy = null
const dispatch = createEventDispatcher()
const onChange = event => {
@ -16,7 +15,6 @@
<div class="spectrum-Switch spectrum-Switch--emphasized">
<input
data-cy={dataCy}
checked={value}
{disabled}
on:change={onChange}

View file

@ -11,7 +11,6 @@
export let readonly = false
export let updateOnChange = true
export let quiet = false
export let dataCy
export let align
export let autofocus = false
@ -89,7 +88,6 @@
{disabled}
{readonly}
{id}
data-cy={dataCy}
value={value || ""}
placeholder={placeholder || ""}
on:click

View file

@ -13,7 +13,6 @@
export let error = null
export let updateOnChange = true
export let quiet = false
export let dataCy
export let autofocus
export let variables
export let showModal
@ -28,7 +27,6 @@
<Field {label} {labelPosition} {error}>
<EnvDropdown
{dataCy}
{updateOnChange}
{error}
{disabled}

View file

@ -13,7 +13,6 @@
export let error = null
export let updateOnChange = true
export let quiet = false
export let dataCy
export let autofocus
const dispatch = createEventDispatcher()
@ -25,7 +24,6 @@
<Field {label} {labelPosition} {error}>
<TextField
{dataCy}
{updateOnChange}
{error}
{disabled}

View file

@ -14,7 +14,6 @@
export let error = null
export let updateOnChange = true
export let quiet = false
export let dataCy
export let autofocus
export let options = []
@ -32,7 +31,6 @@
<Field {label} {labelPosition} {error}>
<InputDropdown
{dataCy}
{updateOnChange}
{error}
{disabled}

View file

@ -21,7 +21,6 @@
export let getSecondaryOptionColour = () => {}
export let getSecondaryOptionIcon = () => {}
export let quiet = false
export let dataCy
export let autofocus
export let primaryOptions = []
export let secondaryOptions = []
@ -98,7 +97,6 @@
<PickerDropdown
{searchTerm}
{autocomplete}
{dataCy}
{error}
{disabled}
{readonly}

View file

@ -9,7 +9,6 @@
export let text = null
export let disabled = false
export let error = null
export let dataCy = null
const dispatch = createEventDispatcher()
const onChange = e => {
@ -19,5 +18,5 @@
</script>
<Field {label} {labelPosition} {error}>
<Switch {dataCy} {error} {disabled} {text} {value} on:change={onChange} />
<Switch {error} {disabled} {text} {value} on:change={onChange} />
</Field>

View file

@ -5,7 +5,6 @@
const dispatch = createEventDispatcher()
const actionMenu = getContext("actionMenu")
export let dataCy
export let icon = undefined
export let disabled = undefined
export let noClose = false
@ -35,7 +34,6 @@
</script>
<li
data-cy={dataCy}
on:click|preventDefault={disabled ? null : onClick}
class="spectrum-Menu-item"
class:is-disabled={disabled}

View file

@ -23,7 +23,7 @@
export let secondaryButtonText = undefined
export let secondaryAction = undefined
export let secondaryButtonWarning = false
export let dataCy = null
const { hide, cancel } = getContext(Context.Modal)
let loading = false
$: confirmDisabled = disabled || loading
@ -63,7 +63,6 @@
role="dialog"
tabindex="-1"
aria-modal="true"
data-cy={dataCy}
>
<div class="spectrum-Dialog-grid">
{#if title || $$slots.header}

View file

@ -13,11 +13,11 @@
export let anchor
export let align = "right"
export let portalTarget
export let dataCy
export let maxWidth
export let open = false
export let useAnchorWidth = false
export let dismissible = true
export let offset = 5
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
@ -57,31 +57,28 @@
</script>
{#if open}
{#key anchor}
<Portal {target}>
<div
tabindex="0"
use:positionDropdown={{
anchor,
align,
maxWidth,
useAnchorWidth,
showTip: false,
}}
use:clickOutside={{
callback: dismissible ? handleOutsideClick : () => {},
anchor,
}}
on:keydown={handleEscape}
class="spectrum-Popover is-open"
role="presentation"
data-cy={dataCy}
transition:fly|local={{ y: -20, duration: 200 }}
>
<slot />
</div>
</Portal>
{/key}
<Portal {target}>
<div
tabindex="0"
use:positionDropdown={{
anchor,
align,
maxWidth,
useAnchorWidth,
offset,
}}
use:clickOutside={{
callback: dismissible ? handleOutsideClick : () => {},
anchor,
}}
on:keydown={handleEscape}
class="spectrum-Popover is-open"
role="presentation"
transition:fly|local={{ y: -20, duration: 200 }}
>
<slot />
</div>
</Portal>
{/if}
<style>

View file

@ -8,7 +8,6 @@
export let icon = ""
export let selected = false
export let disabled = false
export let dataCy
export let badge = ""
</script>
@ -17,7 +16,6 @@
class:is-selected={selected}
class:is-disabled={disabled}
on:click
data-cy={dataCy}
>
{#if heading}
<h2 class="spectrum-SideNav-heading" id="nav-heading-{heading}">

View file

@ -1,15 +0,0 @@
{
"baseUrl": "http://localhost:4100",
"video": true,
"projectId": "bmbemn",
"reporter": "cypress-multi-reporters",
"reporterOptions": {
"configFile": "reporterConfig.json"
},
"env": {
"PORT": "4100",
"WORKER_PORT": "4200",
"JWT_SECRET": "test",
"HOST_IP": ""
}
}

View file

@ -1,3 +0,0 @@
{
"budibase": "CB373643-3FC4-4902-9E31-449C0ED066B6"
}

View file

@ -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"
}

File diff suppressed because one or more lines are too long

View file

@ -1,5 +0,0 @@
{
"id": 8739,
"name": "Jane",
"email": "jane@example.com"
}

View file

@ -1,232 +0,0 @@
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
},
{
"id": 2,
"name": "Ervin Howell",
"username": "Antonette",
"email": "Shanna@melissa.tv",
"address": {
"street": "Victor Plains",
"suite": "Suite 879",
"city": "Wisokyburgh",
"zipcode": "90566-7771",
"geo": {
"lat": "-43.9509",
"lng": "-34.4618"
}
},
"phone": "010-692-6593 x09125",
"website": "anastasia.net",
"company": {
"name": "Deckow-Crist",
"catchPhrase": "Proactive didactic contingency",
"bs": "synergize scalable supply-chains"
}
},
{
"id": 3,
"name": "Clementine Bauch",
"username": "Samantha",
"email": "Nathan@yesenia.net",
"address": {
"street": "Douglas Extension",
"suite": "Suite 847",
"city": "McKenziehaven",
"zipcode": "59590-4157",
"geo": {
"lat": "-68.6102",
"lng": "-47.0653"
}
},
"phone": "1-463-123-4447",
"website": "ramiro.info",
"company": {
"name": "Romaguera-Jacobson",
"catchPhrase": "Face to face bifurcated interface",
"bs": "e-enable strategic applications"
}
},
{
"id": 4,
"name": "Patricia Lebsack",
"username": "Karianne",
"email": "Julianne.OConner@kory.org",
"address": {
"street": "Hoeger Mall",
"suite": "Apt. 692",
"city": "South Elvis",
"zipcode": "53919-4257",
"geo": {
"lat": "29.4572",
"lng": "-164.2990"
}
},
"phone": "493-170-9623 x156",
"website": "kale.biz",
"company": {
"name": "Robel-Corkery",
"catchPhrase": "Multi-tiered zero tolerance productivity",
"bs": "transition cutting-edge web services"
}
},
{
"id": 5,
"name": "Chelsey Dietrich",
"username": "Kamren",
"email": "Lucio_Hettinger@annie.ca",
"address": {
"street": "Skiles Walks",
"suite": "Suite 351",
"city": "Roscoeview",
"zipcode": "33263",
"geo": {
"lat": "-31.8129",
"lng": "62.5342"
}
},
"phone": "(254)954-1289",
"website": "demarco.info",
"company": {
"name": "Keebler LLC",
"catchPhrase": "User-centric fault-tolerant solution",
"bs": "revolutionize end-to-end systems"
}
},
{
"id": 6,
"name": "Mrs. Dennis Schulist",
"username": "Leopoldo_Corkery",
"email": "Karley_Dach@jasper.info",
"address": {
"street": "Norberto Crossing",
"suite": "Apt. 950",
"city": "South Christy",
"zipcode": "23505-1337",
"geo": {
"lat": "-71.4197",
"lng": "71.7478"
}
},
"phone": "1-477-935-8478 x6430",
"website": "ola.org",
"company": {
"name": "Considine-Lockman",
"catchPhrase": "Synchronised bottom-line interface",
"bs": "e-enable innovative applications"
}
},
{
"id": 7,
"name": "Kurtis Weissnat",
"username": "Elwyn.Skiles",
"email": "Telly.Hoeger@billy.biz",
"address": {
"street": "Rex Trail",
"suite": "Suite 280",
"city": "Howemouth",
"zipcode": "58804-1099",
"geo": {
"lat": "24.8918",
"lng": "21.8984"
}
},
"phone": "210.067.6132",
"website": "elvis.io",
"company": {
"name": "Johns Group",
"catchPhrase": "Configurable multimedia task-force",
"bs": "generate enterprise e-tailers"
}
},
{
"id": 8,
"name": "Nicholas Runolfsdottir V",
"username": "Maxime_Nienow",
"email": "Sherwood@rosamond.me",
"address": {
"street": "Ellsworth Summit",
"suite": "Suite 729",
"city": "Aliyaview",
"zipcode": "45169",
"geo": {
"lat": "-14.3990",
"lng": "-120.7677"
}
},
"phone": "586.493.6943 x140",
"website": "jacynthe.com",
"company": {
"name": "Abernathy Group",
"catchPhrase": "Implemented secondary concept",
"bs": "e-enable extensible e-tailers"
}
},
{
"id": 9,
"name": "Glenna Reichert",
"username": "Delphine",
"email": "Chaim_McDermott@dana.io",
"address": {
"street": "Dayna Park",
"suite": "Suite 449",
"city": "Bartholomebury",
"zipcode": "76495-3109",
"geo": {
"lat": "24.6463",
"lng": "-168.8889"
}
},
"phone": "(775)976-6794 x41206",
"website": "conrad.com",
"company": {
"name": "Yost and Sons",
"catchPhrase": "Switchable contextually-based project",
"bs": "aggregate real-time technologies"
}
},
{
"id": 10,
"name": "Clementina DuBuque",
"username": "Moriah.Stanton",
"email": "Rey.Padberg@karina.biz",
"address": {
"street": "Kattie Turnpike",
"suite": "Suite 198",
"city": "Lebsackbury",
"zipcode": "31428-2261",
"geo": {
"lat": "-38.2386",
"lng": "57.2232"
}
},
"phone": "024-648-3804",
"website": "ambrose.net",
"company": {
"name": "Hoeger LLC",
"catchPhrase": "Centralized empowering task-force",
"bs": "target end-to-end tables"
}
}
]

View file

@ -1,45 +0,0 @@
import filterTests from "../support/filterTests"
const interact = require('../support/interact')
filterTests(['all'], () => {
xcontext("Add Multi-Option Datatype", () => {
before(() => {
cy.login()
cy.createTestApp()
})
it("should create a new table, with data", () => {
cy.createTable("Multi Data")
cy.addColumn("Multi Data", "Test Data", "Multi-select", "1\n2\n3\n4\n5")
cy.addRowMultiValue(["1", "2", "3", "4", "5"])
})
it("should add form with multi select picker, containing 5 options", () => {
cy.navigateToFrontend()
// Add data provider
cy.searchAndAddComponent("Data Provider")
cy.get(interact.DATASOURCE_PROP_CONTROL).click()
cy.get(interact.DROPDOWN).contains("Multi Data").click()
// Add Form with schema to match table
cy.searchAndAddComponent("Form")
cy.get(interact.DATASOURCE_PROP_CONTROL).click()
cy.get(interact.DROPDOWN).contains("Multi Data").click()
// Add multi-select picker to form
cy.searchAndAddComponent("Multi-select Picker").then(componentId => {
cy.get(interact.DATASOURCE_FIELD_CONTROL).type("Test Data").type("{enter}")
cy.wait(1000)
cy.getComponent(componentId).contains("Choose some options").click()
// Check picker has 5 items
cy.getComponent(componentId).find("li").should("have.length", 5)
// Select all items
for (let i = 1; i < 6; i++) {
cy.getComponent(componentId).find("li").contains(i).click()
}
// Check items have been selected
cy.getComponent(componentId)
.find(interact.SPECTRUM_PICKER_LABEL)
.contains("(5)")
})
})
})
})

View file

@ -1,43 +0,0 @@
import filterTests from "../support/filterTests"
const interact = require('../support/interact')
filterTests(['all'], () => {
xcontext("Add Radio Buttons", () => {
before(() => {
cy.login()
cy.createTestApp()
})
it("should add Radio Buttons options picker on form, add data, and confirm", () => {
cy.navigateToFrontend()
cy.searchAndAddComponent("Form")
cy.searchAndAddComponent("Options Picker").then((componentId) => {
// Provide field setting
cy.get(interact.DATASOURCE_FIELD_CONTROL).type("1")
// Open dropdown and select Radio buttons
cy.get(interact.OPTION_TYPE_PROP_CONTROL).click().then(() => {
cy.get(interact.SPECTRUM_POPOVER).contains('Radio buttons')
.click()
})
const radioButtonsTotal = 3
// Add values and confirm total
addRadioButtonData(radioButtonsTotal)
cy.getComponent(componentId).find('[type="radio"]')
.should('have.length', radioButtonsTotal)
})
})
const addRadioButtonData = (totalRadioButtons) => {
cy.get(interact.OPTION_SOURCE_PROP_CONROL).click().then(() => {
cy.get(interact.SPECTRUM_POPOVER).contains('Custom')
.click()
.wait(1000)
})
cy.addCustomSourceOptions(totalRadioButtons)
}
after(() => {
cy.deleteAllApps()
})
})
})

View file

@ -1,116 +0,0 @@
import filterTests from "../../support/filterTests"
const interact = require('../../support/interact')
filterTests(["smoke", "all"], () => {
xcontext("Account Portals", () => {
const bbUserEmail = "bbuser@test.com"
before(() => {
cy.login()
cy.deleteApp("Cypress Tests")
cy.createApp("Cypress Tests", false)
// Create new user
cy.wait(500)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.createUser(bbUserEmail)
cy.contains("bbuser").click()
cy.wait(500)
// Reset password
cy.get(".title").within(() => {
cy.get(interact.SPECTRUM_ICON).click({ force: true })
})
cy.get(interact.SPECTRUM_MENU).within(() => {
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force password reset").click({ force: true })
})
cy.get(interact.SPECTRUM_DIALOG_GRID)
.find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('pwd')
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").click({ force: true })
// Login as new user and set password
cy.logOut()
cy.get('@pwd').then((pwd) => {
cy.login(bbUserEmail, pwd)
})
for (let i = 0; i < 2; i++) {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test")
}
cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true })
//cy.logoutNoAppGrid()
})
xit("should verify Standard Portal", () => {
// Development access should be disabled (Admin access is already disabled)
cy.login()
cy.setUserRole("bbuser", "App User")
bbUserLogin()
// Verify Standard Portal
cy.get(interact.SPECTRUM_SIDENAV).should('not.exist') // No config sections
cy.get(interact.CREATE_APP_BUTTON).should('not.exist') // No create app button
cy.get(".app").should('not.exist') // No apps -> no roles assigned to user
cy.get(interact.CONTAINER).should('contain', bbUserEmail) // Message containing users email
cy.logoutNoAppGrid()
})
xit("should verify Admin Portal", () => {
cy.login()
// Configure user role
cy.setUserRole("bbuser", "Admin")
bbUserLogin()
// Verify available options for Admin portal
cy.get(interact.SPECTRUM_SIDENAV)
.should('contain', 'Apps')
//.and('contain', 'Usage')
.and('contain', 'Users')
.and('contain', 'Auth')
.and('contain', 'Email')
.and('contain', 'Organisation')
.and('contain', 'Theming')
.and('contain', 'Update')
//.and('contain', 'Upgrade')
cy.logOut()
})
xit("should verify Development Portal", () => {
// Only Development access should be enabled
cy.login()
cy.setUserRole("bbuser", "Developer")
bbUserLogin()
// Verify available options for Admin portal
cy.get(interact.SPECTRUM_SIDENAV)
.should('contain', 'Apps')
//.and('contain', 'Usage')
.and('not.contain', 'Users')
.and('not.contain', 'Auth')
.and('not.contain', 'Email')
.and('not.contain', 'Organisation')
.and('contain', 'Theming')
.and('not.contain', 'Update')
.and('not.contain', 'Upgrade')
cy.logOut()
})
const bbUserLogin = () => {
// Login as bbuser
cy.logOut()
cy.login(bbUserEmail, "test")
}
after(() => {
cy.login()
// Delete BB user
cy.deleteUser(bbUserEmail)
})
})
})

View file

@ -1,178 +0,0 @@
import filterTests from "../../support/filterTests"
// const interact = require("../support/interact")
filterTests(["smoke", "all"], () => {
xcontext("Auth Configuration", () => {
before(() => {
cy.login()
})
after(() => {
cy.get(".spectrum-SideNav li").contains("Auth").click()
cy.location().should(loc => {
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
})
cy.get("[data-cy=new-scope-input]").clear()
cy.get("div.content").scrollTo("bottom")
cy.get("[data-cy=oidc-active]").click()
cy.get("[data-cy=oidc-active]").should('not.be.checked')
cy.intercept("POST", "/api/global/configs").as("updateAuth")
cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true })
cy.wait("@updateAuth")
cy.get("@updateAuth").its("response.statusCode").should("eq", 200)
cy.get(".spectrum-Toast-content")
.contains("Settings saved")
.should("be.visible")
})
it("Should allow updating of the OIDC config", () => {
cy.get(".spectrum-SideNav li").contains("Auth").click()
cy.location().should(loc => {
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
})
cy.get("div.content").scrollTo("bottom")
cy.get(".spectrum-Toast .spectrum-ClearButton").click()
cy.get("input[data-cy=configUrl]").type("http://budi-auth.com/v2")
cy.get("input[data-cy=clientID]").type("34ac6a13-f24a-4b52-c70d-fa544ffd11b2")
cy.get("input[data-cy=clientSecret]").type("12A8Q~4nS_DWhOOJ2vWIRsNyDVsdtXPD.Zxa9df_")
cy.get("button[data-cy=oidc-save]").should("not.be.disabled");
cy.intercept("POST", "/api/global/configs").as("updateAuth")
cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true })
cy.wait("@updateAuth")
cy.get("@updateAuth").its("response.statusCode").should("eq", 200)
cy.get(".spectrum-Toast-content")
.contains("Settings saved")
.should("be.visible")
})
it("Should display default scopes in advanced config.", () => {
cy.get(".spectrum-SideNav li").contains("Auth").click()
cy.location().should(loc => {
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
})
cy.get("div.content").scrollTo("bottom")
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
cy.get(".spectrum-Tags-item").contains("openid")
cy.get(".spectrum-Tags-item").contains("openid").find(".spectrum-ClearButton").should("not.exist")
cy.get(".spectrum-Tags-item").contains("offline_access")
cy.get(".spectrum-Tags-item").contains("email")
cy.get(".spectrum-Tags-item").contains("profile")
})
it("Add a new scopes", () => {
cy.get(".spectrum-SideNav li").contains("Auth").click()
cy.location().should(loc => {
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
})
cy.get("div.content").scrollTo("bottom")
cy.get("[data-cy=new-scope-input]").type("Sample{enter}")
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 5)
cy.get(".spectrum-Tags-item").contains("Sample")
cy.get(".auth-form input.spectrum-Textfield-input").type("Another ")
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 6)
cy.get(".spectrum-Tags-item").contains("Another")
cy.get("button[data-cy=oidc-save]").should("not.be.disabled");
cy.intercept("POST", "/api/global/configs").as("updateAuth")
cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true })
cy.wait("@updateAuth")
cy.get("@updateAuth").its("response.statusCode").should("eq", 200)
cy.reload()
cy.get("div.content").scrollTo("bottom")
cy.get(".spectrum-Tags-item").contains("openid")
cy.get(".spectrum-Tags-item").contains("offline_access")
cy.get(".spectrum-Tags-item").contains("email")
cy.get(".spectrum-Tags-item").contains("profile")
cy.get(".spectrum-Tags-item").contains("Sample")
cy.get(".spectrum-Tags-item").contains("Another")
})
it("Should allow the removal of auth scopes", () => {
cy.get(".spectrum-SideNav li").contains("Auth").click()
cy.location().should(loc => {
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
})
cy.get("div.content").scrollTo("bottom")
cy.get(".spectrum-Tags-item").contains("offline_access").parent().find(".spectrum-ClearButton").click()
cy.get(".spectrum-Tags-item").contains("profile").parent().find(".spectrum-ClearButton").click()
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
cy.get(".spectrum-Tags-item").contains("offline_access").should("not.exist")
cy.get(".spectrum-Tags-item").contains("profile").should("not.exist")
cy.get("button[data-cy=oidc-save]").should("not.be.disabled");
cy.intercept("POST", "/api/global/configs").as("updateAuth")
cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true })
cy.wait("@updateAuth")
cy.get("@updateAuth").its("response.statusCode").should("eq", 200)
cy.get(".spectrum-Toast-content")
.contains("Settings saved")
.should("be.visible")
cy.reload()
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
cy.get(".spectrum-Tags-item").contains("offline_access").should("not.exist")
cy.get(".spectrum-Tags-item").contains("profile").should("not.exist")
})
it("Should allow auth scopes to be reset to the core defaults.", () => {
cy.get(".spectrum-SideNav li").contains("Auth").click()
cy.get("div.content").scrollTo("bottom")
cy.get("[data-cy=restore-oidc-default-scopes]").click({ force: true })
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
cy.get(".spectrum-Tags-item").contains("openid")
cy.get(".spectrum-Tags-item").contains("offline_access")
cy.get(".spectrum-Tags-item").contains("email")
cy.get(".spectrum-Tags-item").contains("profile")
})
it("Should not allow invalid characters in the auth scopes", () => {
cy.get("[data-cy=new-scope-input]").type("thisIsInvalid\\{enter}")
cy.get(".spectrum-Form-itemField .error").contains("Auth scopes cannot contain spaces, double quotes or backslashes")
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
cy.get("[data-cy=new-scope-input]").clear()
cy.get("[data-cy=new-scope-input]").type("alsoInvalid\"{enter}")
cy.get(".spectrum-Form-itemField .error").contains("Auth scopes cannot contain spaces, double quotes or backslashes")
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
cy.get("[data-cy=new-scope-input]").clear()
})
it("Should not allow duplicate auth scopes", () => {
cy.get("[data-cy=new-scope-input]").type("offline_access{enter}")
cy.get(".spectrum-Form-itemField .error").contains("Auth scope already exists")
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
})
})
})

View file

@ -1,238 +0,0 @@
import filterTests from "../../support/filterTests"
const interact = require('../../support/interact')
filterTests(["smoke", "all"], () => {
xcontext("User Management", () => {
before(() => {
cy.login()
cy.deleteApp("Cypress Tests")
cy.createApp("Cypress Tests", false)
})
xit("should create a user via basic onboarding", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000})
cy.createUser("bbuser@test.com")
cy.get(interact.SPECTRUM_TABLE).should("contain", "bbuser")
})
xit("should confirm App User role for a New User", () => {
cy.contains("bbuser").click()
cy.get(".spectrum-Form-itemField").eq(3).should('contain', 'App User')
// User should not have app access
cy.get(".spectrum-Heading").contains("Apps").parent().within(() => {
cy.get(interact.LIST_ITEMS, { timeout: 500 }).should("contain", "This user has access to no apps")
})
})
if (Cypress.env("TEST_ENV")) {
xit("should assign role types", () => {
// 3 apps minimum required - to assign an app to each role type
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length < 3) {
for (let i = 1; i < 3; i++) {
const uuid = () => Cypress._.random(0, 1e6)
const name = uuid()
if(i < 1){
cy.createApp(name, false)
} else {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000})
cy.wait(1000)
cy.get(interact.CREATE_APP_BUTTON, { timeout: 2000 }).click({ force: true })
cy.createAppFromScratch(name)
}
}
}
})
// Navigate back to the user
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000})
cy.get(interact.SPECTRUM_SIDENAV).contains("Users").click()
cy.get(interact.SPECTRUM_TABLE, { timeout: 1000 }).contains("bbuser").click()
cy.get(interact.SPECTRUM_HEADING).contains("bbuser", { timeout: 2000})
for (let i = 0; i < 3; i++) {
cy.get(interact.SPECTRUM_TABLE, { timeout: 3000})
.eq(1)
.find(interact.SPECTRUM_TABLE_ROW)
.eq(0)
.find(interact.SPECTRUM_TABLE_CELL)
.eq(0)
.click()
cy.get(interact.SPECTRUM_DIALOG_GRID, { timeout: 1000 })
.contains("Choose an option")
.click()
.then(() => {
if (i == 0) {
cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Admin").click({ force: true })
}
else if (i == 1) {
cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Power").click({ force: true })
}
else if (i == 2) {
cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Basic").click({ force: true })
}
cy.get(interact.SPECTRUM_BUTTON, { timeout: 2000 })
.contains("Update role")
.click({ force: true })
})
cy.reload()
cy.wait(1000)
}
// Confirm roles exist within Configure roles table
cy.get(interact.SPECTRUM_TABLE, { timeout: 20000 })
.eq(0)
.within(assginedRoles => {
expect(assginedRoles).to.contain("Admin")
expect(assginedRoles).to.contain("Power")
expect(assginedRoles).to.contain("Basic")
})
})
xit("should unassign role types", () => {
// Set each app within Configure roles table to 'No Access'
cy.get(interact.SPECTRUM_TABLE)
.eq(0)
.find(interact.SPECTRUM_TABLE_ROW)
.its("length")
.then(len => {
for (let i = 0; i < len; i++) {
cy.get(interact.SPECTRUM_TABLE)
.eq(0)
.find(interact.SPECTRUM_TABLE_ROW)
.eq(0)
.find(interact.SPECTRUM_TABLE_CELL)
.eq(0)
.click()
.then(() => {
cy.get(interact.SPECTRUM_PICKER).eq(1).click({ force: true })
cy.get(interact.SPECTRUM_POPOVER, { timeout: 500 }).contains("No Access").click()
})
cy.get(interact.SPECTRUM_BUTTON)
.contains("Update role")
.click({ force: true })
}
})
// Confirm Configure roles table no longer has any apps in it
cy.get(interact.SPECTRUM_TABLE, { timeout: 1000 }).eq(0).contains("No rows found")
})
}
xit("should enable Developer access and verify application access", () => {
// Enable Developer access
cy.get(interact.FIELD)
.eq(4)
.within(() => {
cy.get(interact.SPECTRUM_SWITCH_INPUT).click({ force: true })
})
// No Access table should now be empty
cy.get(interact.CONTAINER)
.contains("No Access")
.parent()
.within(() => {
cy.get(interact.SPECTRUM_TABLE).contains("No rows found")
})
// Each app within Configure roles should have Admin access
cy.get(interact.SPECTRUM_TABLE)
.eq(0)
.find(interact.SPECTRUM_TABLE_ROW)
.its("length")
.then(len => {
for (let i = 0; i < len; i++) {
cy.get(interact.SPECTRUM_TABLE)
.eq(0)
.find(interact.SPECTRUM_TABLE_ROW)
.eq(i)
.contains("Admin")
cy.wait(500)
}
})
})
xit("should disable Developer access and verify application access", () => {
// Disable Developer access
cy.get(interact.FIELD)
.eq(4)
.within(() => {
cy.get(".spectrum-Switch-input").click({ force: true })
})
// Configure roles table should now be empty
cy.get(interact.CONTAINER)
.contains("Configure roles")
.parent()
.within(() => {
cy.get(interact.SPECTRUM_TABLE).contains("No rows found")
})
})
xit("Should edit user details within user details page", () => {
// Add First name
cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => {
cy.wait(500)
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).wait(500).clear().click().type("bb")
})
// Add Last name
cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => {
cy.wait(500)
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).click().wait(500).clear().type("test")
})
cy.get(interact.FIELD, { timeout: 1000 }).eq(0).click()
// Reload page
cy.reload()
// Confirm details have been saved
cy.get(interact.FIELD, { timeout: 20000 }).eq(1).within(() => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', "bb")
})
cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).should('have.value', "test")
})
})
xit("should reset the users password", () => {
cy.get(".title").within(() => {
cy.get(interact.SPECTRUM_ICON).click({ force: true })
})
cy.get(interact.SPECTRUM_MENU).within(() => {
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force password reset").click({ force: true })
})
// Reset password modal
cy.get(interact.SPECTRUM_DIALOG_GRID)
.find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('pwd')
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").click({ force: true })
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").should('not.exist')
// Logout, then login with new password
cy.logOut()
cy.get('@pwd').then((pwd) => {
cy.login("bbuser@test.com", pwd)
})
// Reset password screen
for (let i = 0; i < 2; i++) {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test")
}
cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true })
// Confirm user logged in afer password change
cy.login("bbuser@test.com", "test")
cy.get(".avatar > .icon").click({ force: true })
cy.get(".spectrum-Menu-item").contains("Update user information").click({ force: true })
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT)
.eq(0)
.invoke('val').should('eq', 'bbuser@test.com')
// Logout and login as previous user
cy.logoutNoAppGrid()
cy.login()
})
xit("should delete a user", () => {
cy.deleteUser("bbuser@test.com")
cy.get(interact.SPECTRUM_TABLE, { timeout: 4000 }).should("not.have.text", "bbuser")
})
})
})

View file

@ -1,114 +0,0 @@
import filterTests from "../../support/filterTests"
const interact = require('../../support/interact')
filterTests(["smoke", "all"], () => {
context("User Settings Menu", () => {
before(() => {
cy.login()
})
it("should update user information via user settings menu", () => {
const fname = "test"
const lname = "user"
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.updateUserInformation(fname, lname)
// Go to user info and confirm name update
cy.contains("Users").click()
cy.contains("test@test.com").click()
cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', fname)
})
cy.get(interact.FIELD).eq(2).within(() => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', lname)
})
})
xit("should allow copying of the users API key", () => {
cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true })
cy.get(interact.SPECTRUM_MENU_ITEM).contains("View API key").click({ force: true })
cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => {
cy.get(interact.SPECTRUM_ICON).click({ force: true })
})
// There may be timing issues with this on the smoke build
cy.wait(500)
cy.get(".spectrum-Toast-content")
.contains("URL copied to clipboard")
.should("be.visible")
})
it("should allow API key regeneration", () => {
cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true })
cy.get(interact.SPECTRUM_MENU_ITEM).contains("View API key").click({ force: true })
cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => {
cy.get(interact.SPECTRUM_ICON).click({ force: true })
})
// Get initial API key value
cy.get(interact.SPECTRUM_DIALOG_CONTENT)
.find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('keyOne')
// Click re-generate key button
cy.get("button").contains("Regenerate key").click({ force: true })
// Verify API key was changed
cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => {
cy.get('@keyOne').then((keyOne) => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').should('not.eq', keyOne)
})
})
cy.closeModal()
})
it("should update password", () => {
// Access Update password modal
cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true })
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Update password").click({ force: true })
// Enter new password and update
cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => {
for (let i = 0; i < 2; i++) {
// password set to 'newpwd'
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("newpwd")
}
cy.get("button").contains("Update password").click({ force: true })
})
// Logout & in with new password
//cy.logOut()
cy.login("test@test.com", "newpwd")
})
xit("should open and close developer mode", () => {
cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true })
// Close developer mode & verify
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Close developer mode").click({ force: true })
cy.get(interact.SPECTRUM_SIDENAV).should('not.exist') // No config sections
cy.get(interact.CREATE_APP_BUTTON).should('not.exist') // No create app button
cy.get(".app").should('not.exist') // At least one app should be available
// Open developer mode & verify
cy.get(".avatar > .icon").click({ force: true })
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Open developer mode").click({ force: true })
cy.get(".app-table").should('exist') // config sections available
cy.get(interact.CREATE_APP_BUTTON).should('exist') // create app button available
})
after(() => {
// Change password back to original value
cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true })
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Update password").click({ force: true })
cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => {
for (let i = 0; i < 2; i++) {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test")
}
cy.get("button").contains("Update password").click({ force: true })
})
// Remove users name
cy.updateUserInformation()
})
})
})

View file

@ -1,442 +0,0 @@
import filterTests from "../support/filterTests"
import clientPackage from "@budibase/client/package.json"
filterTests(["all"], () => {
xcontext("Application Overview screen", () => {
before(() => {
cy.login()
cy.deleteAllApps()
cy.createApp("Cypress Tests")
})
xit("Should be accessible from the applications list", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .title")
.eq(0)
.invoke("attr", "data-cy")
.then($dataCy => {
const dataCy = $dataCy
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.click({ force: true })
cy.location().should(loc => {
expect(loc.pathname).to.eq("/builder/portal/overview/" + dataCy)
})
})
})
// Find a more suitable place for this.
xit("Should allow unlocking in the app list", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .lock-status").eq(0).contains("Locked by you").click()
cy.unlockApp({ owned: true })
cy.get(".appTable").should("exist")
cy.get(".lock-status").should("not.be.visible")
})
xit("Should allow unlocking in the app overview screen", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Edit")
.eq(0)
.click({ force: true })
cy.wait(1000)
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(".lock-status").eq(0).contains("Locked by you").click()
cy.unlockApp({ owned: true })
cy.get(".lock-status").should("not.be.visible")
})
xit("Should reflect the deploy state of an app that hasn't been published.", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(".header-right button.spectrum-Button[data-cy='view-app']").should(
"be.disabled"
)
cy.get(".spectrum-Tabs-item.is-selected").contains("Overview")
cy.get(".overview-tab").should("be.visible")
cy.get(".overview-tab [data-cy='app-status']").within(() => {
cy.get(".status-display").contains("Unpublished")
cy.get(".status-display .icon svg[aria-label='GlobeStrike']").should(
"exist"
)
cy.get(".status-text").contains("-")
})
})
xit("Should reflect the app deployment state", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.get(".appTable .app-row-actions button")
.contains("Edit")
.eq(0)
.click({ force: true })
cy.wait(500)
cy.get(".toprightnav button.spectrum-Button", { timeout: 2000 })
.contains("Publish")
.click({ force: true })
cy.get(".spectrum-Modal [data-cy='deploy-app-modal']")
.should("be.visible")
.within(() => {
cy.get(".spectrum-Button").contains("Publish").click({ force: true })
cy.wait(1000)
})
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(".header-right button.spectrum-Button[data-cy='view-app']").should(
"not.be.disabled"
)
cy.get(".overview-tab [data-cy='app-status']").within(() => {
cy.get(".status-display").contains("Published")
cy.get(".status-display .icon svg[aria-label='GlobeCheck']").should(
"exist"
)
cy.get(".status-text").contains("Last published a few seconds ago")
})
})
xit("Should reflect an application that has been unpublished", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Edit")
.eq(0)
.click({ force: true })
cy.get(".deployment-top-nav svg[aria-label='Globe']").click({
force: true,
})
cy.get("[data-cy='publish-popover-menu']").should("be.visible")
cy.get(
"[data-cy='publish-popover-menu'] [data-cy='publish-popover-action']"
).click({ force: true })
cy.get("[data-cy='unpublish-modal']")
.should("be.visible")
.within(() => {
cy.get(".confirm-wrap button").click({ force: true })
})
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.wait(1000)
cy.get(".appTable .app-row-actions button", { timeout: 10000 })
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(".overview-tab [data-cy='app-status']").within(() => {
cy.get(".status-display").contains("Unpublished")
cy.get(".status-display .icon svg[aria-label='GlobeStrike']").should(
"exist"
)
cy.get(".status-text").contains("Last published a few seconds ago")
})
})
xit("Should allow the editing of the application icon and colour", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(".edit-hover", { timeout: 1000 }).eq(0).click({ force: true })
// Select random icon
cy.wait(400)
cy.get(".grid").within(() => {
cy.get(".icon-item")
.eq(Math.floor(Math.random() * 23) + 1)
.click()
})
// Select random colour
cy.get(".fill").click()
cy.get(".colors").within(() => {
cy.get(".color")
.eq(Math.floor(Math.random() * 33) + 1)
.click()
})
cy.intercept("**/applications/**").as("iconChange")
cy.get(".spectrum-Button").contains("Save").click({ force: true })
cy.wait("@iconChange")
cy.get("@iconChange").its("response.statusCode").should("eq", 200)
// Confirm icon has changed from default
// Confirm colour has been applied
cy.get(".spectrum-ActionButton-label").contains("Back").click({ force: true })
cy.get(".appTable", { timeout: 2000 }).within(() => {
cy.get("[aria-label]")
.eq(0)
.children()
.should("have.attr", "xlink:href")
.and("not.contain", "#spectrum-icon-18-Apps")
cy.get(".title")
.children()
.children()
.should("have.attr", "style")
.and("contains", "color")
})
})
xit("Should reflect the last time the application was edited", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(".header-right button").contains("Edit").click({ force: true })
cy.navigateToFrontend()
cy.searchAndAddComponent("Headline").then(componentId => {
cy.getComponent(componentId).should("exist")
})
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(".overview-tab [data-cy='edited-by']").within(() => {
cy.get(".editor-name").contains("You")
cy.get(".last-edit-text").contains("Last edited a few seconds ago")
})
})
xit("Should reflect application version is up-to-date", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(".overview-tab [data-cy='app-version']").within(() => {
cy.get(".version-status").contains("You're running the latest!")
})
})
it("Should navigate to the settings tab when clicking the App Version card header", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(".spectrum-Tabs-item.is-selected").contains("Overview")
cy.get(".overview-tab").should("be.visible")
cy.get(".overview-tab [data-cy='app-version'] .dash-card-header").click({
force: true,
})
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
cy.get(".settings-tab").should("be.visible")
cy.get(".overview-tab").should("not.exist")
})
it("Should allow the upgrading of an application, if available.", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
cy.wait(500)
cy.location().then(loc => {
const params = loc.pathname.split("/")
const appId = params[params.length - 1]
cy.log(appId)
//Downgrade the app for the test
cy.alterAppVersion(appId, "0.0.1-alpha.0").then(() => {
cy.reload()
cy.log("Current deployment version: " + clientPackage.version)
cy.get(".version-status a", { timeout: 5000 }).contains("Update").click()
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
cy.get(".version-section .page-action button")
.contains("Update")
.click({ force: true })
cy.intercept("POST", "**/applications/**/client/update").as(
"updateVersion"
)
cy.get(".spectrum-Modal.is-open button")
.contains("Update")
.click({ force: true })
cy.wait("@updateVersion")
.its("response.statusCode")
.should("eq", 200)
.then(() => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(".spectrum-Tabs-item")
.contains("Overview")
.click({ force: true })
cy.get(".overview-tab [data-cy='app-version']").within(() => {
cy.get(".spectrum-Heading").contains(clientPackage.version)
cy.get(".version-status").contains("You're running the latest!")
})
})
})
})
})
xit("Should allow editing of the app details.", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(".spectrum-Tabs-item").contains("Settings").click()
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
cy.get(".settings-tab").should("be.visible")
cy.get(".details-section .page-action button")
.contains("Edit")
.click({ force: true })
cy.updateAppName("sample name")
//publish and check its disabled
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.wait(500)
cy.get(".appTable .app-row-actions button")
.contains("Edit")
.eq(0)
.click({ force: true })
cy.get(".toprightnav button.spectrum-Button")
.contains("Publish")
.click({ force: true })
cy.get(".spectrum-Modal [data-cy='deploy-app-modal']")
.should("be.visible")
.within(() => {
cy.get(".spectrum-Button").contains("Publish").click({ force: true })
cy.wait(1000)
})
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 10000 })
cy.get(".appTable .app-row-actions button", { timeout: 5000 })
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(".spectrum-Tabs-item").contains("Settings").click()
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
cy.get(".details-section .page-action .spectrum-Button").scrollIntoView()
cy.get(".details-section .page-action .spectrum-Button", { timeout: 1000 }).should(
"be.disabled"
)
})
xit("Should allow copying of the published application Id", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions")
.eq(0)
.within(() => {
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
})
cy.publishApp("sample-name")
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(".app-overview-actions-icon > .icon").click({ force: true })
cy.get("[data-cy='app-overview-menu-popover']")
.eq(0)
.within(() => {
cy.get(".spectrum-Menu-item")
.contains("Copy App ID")
.click({ force: true })
})
cy.get(".spectrum-Toast-content")
.contains("App ID copied to clipboard.")
.should("be.visible")
})
xit("Should allow unpublishing of the application via the Unpublish link", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(`[data-cy="app-status"]`).within(() => {
cy.contains("Unpublish").click({ force: true })
})
cy.get("[data-cy='unpublish-modal']")
.should("be.visible")
.within(() => {
cy.get(".confirm-wrap button").click({ force: true })
})
cy.get(".overview-tab [data-cy='app-status']").within(() => {
cy.get(".status-display").contains("Unpublished")
cy.get(".status-display .icon svg[aria-label='GlobeStrike']")
.should("exist")
})
})
xit("Should allow deleting of the application", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
cy.get(".app-overview-actions-icon > .icon").click({ force: true })
cy.get("[data-cy='app-overview-menu-popover']")
.eq(0)
.within(() => {
cy.get(".spectrum-Menu-item")
.contains("Delete")
.click({ force: true })
cy.wait(500)
})
//The test application was renamed earlier in the spec
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get("input").type("sample name")
cy.get(".spectrum-Button--warning").click()
})
cy.location().should(loc => {
expect(loc.pathname).to.eq("/builder/portal/apps")
})
cy.get(".appTable").should("not.exist")
cy.get(".welcome .container h1").contains("Let's create your first app!")
})
after(() => {
cy.deleteAllApps()
})
})
})

View file

@ -1,108 +0,0 @@
import filterTests from "../support/filterTests"
import { APP_TABLE_APP_NAME, DEPLOY_SUCCESS_MODAL } from "../support/interact";
const interact = require('../support/interact')
filterTests(['all'], () => {
xcontext("Publish Application Workflow", () => {
before(() => {
cy.login()
cy.deleteAllApps()
cy.createApp("Cypress Tests", false)
})
xit("Should reflect the unpublished status correctly", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.get(interact.APP_TABLE_STATUS, { timeout: 3000 }).eq(0)
.within(() => {
cy.contains("Unpublished")
cy.get(interact.GLOBESTRIKE).should("exist")
})
cy.get(interact.APP_TABLE_ROW_ACTION).eq(0)
.within(() => {
cy.get(interact.SPECTRUM_BUTTON_TEMPLATE).contains("Edit").click({ force: true })
})
cy.get(interact.DEPLOYMENT_TOP_NAV_GLOBESTRIKE).should("exist")
cy.get(interact.DEPLOYMENT_TOP_GLOBE).should("not.exist")
})
xit("Should publish an application and correctly reflect that", () => {
//Assuming the previous test was run and the unpublished app is open in edit mode.
cy.get(interact.TOPRIGHTNAV_BUTTON_SPECTRUM).contains("Publish").click({ force: true })
cy.get(interact.DEPLOY_APP_MODAL).should("be.visible")
.within(() => {
cy.get(interact.SPECTRUM_BUTTON).contains("Publish").click({ force: true })
});
//Verify that the app url is presented correctly to the user
cy.get(interact.DEPLOY_SUCCESS_MODAL, { timeout: 1000 })
.should("be.visible")
.within(() => {
let appUrl = Cypress.config().baseUrl + '/app/cypress-tests'
cy.get(interact.DEPLOY_APP_URL_INPUT).should('have.value', appUrl)
cy.get(interact.SPECTRUM_BUTTON).contains("Done").click({ force: true })
})
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(interact.APP_TABLE_STATUS, { timeout: 3000 }).eq(0)
.within(() => {
cy.contains("Published")
cy.get(interact.GLOBE).should("exist")
})
cy.get(interact.APP_TABLE_ROW_ACTION).eq(0)
.within(() => {
cy.get(interact.SPECTRUM_BUTTON).contains("Manage")
cy.get(interact.SPECTRUM_BUTTON).contains("Edit").click({ force: true })
})
cy.get(interact.DEPLOYMENT_TOP_GLOBE).should("exist").click({ force: true })
cy.get(interact.PUBLISH_POPOVER_MENU).should("be.visible")
.within(() => {
cy.get(interact.PUBLISH_POPOVER_ACTION).should("exist")
cy.get("button").contains("View app").should("exist")
cy.get(interact.PUBLISH_POPOVER_MESSAGE).should("have.text", "Last published a few seconds ago")
})
})
xit("Should unpublish an application using the link and reflect the status change", () => {
//Assuming the previous test app exists and is published
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.get(interact.APP_TABLE_STATUS).eq(0)
.within(() => {
cy.contains("Published")
cy.get("svg[aria-label='Globe']").should("exist")
})
cy.get(interact.APP_TABLE).eq(0)
.within(() => {
cy.get(interact.APP_TABLE_APP_NAME).click({ force: true })
})
cy.get(interact.DEPLOYMENT_TOP_GLOBE).should("exist").click({ force: true })
cy.get("[data-cy='publish-popover-menu']")
.within(() => {
cy.get(interact.PUBLISH_POPOVER_ACTION).click({ force: true })
})
cy.get(interact.UNPUBLISH_MODAL).should("be.visible")
.within(() => {
cy.get(interact.CONFIRM_WRAP_BUTTON).click({ force: true }
)
})
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 6000 })
cy.wait(500)
cy.get(interact.APP_TABLE_STATUS, { timeout: 10000 }).eq(0).contains("Unpublished")
})
})
})

View file

@ -1,101 +0,0 @@
import filterTests from "../support/filterTests"
const interact = require('../support/interact')
filterTests(['smoke', 'all'], () => {
xcontext("Auto Screens UI", () => {
before(() => {
cy.login()
cy.deleteAllApps()
})
it("should disable the autogenerated screen options if no sources are available", () => {
cy.createApp("First Test App", false)
cy.closeModal();
cy.navigateToAutogeneratedModal()
cy.get(interact.CONFIRM_WRAP_SPE_BUTTON).should('be.disabled')
cy.deleteAllApps()
});
it("should not display incompatible sources", () => {
cy.createApp("Test App")
cy.selectExternalDatasource("REST")
cy.selectExternalDatasource("S3")
cy.get(interact.SPECTRUM_MODAL).within(() => {
cy.get(interact.SPECTRUM_BUTTON).contains("Save and continue to query").click({ force : true })
})
cy.navigateToAutogeneratedModal()
cy.get(interact.DATA_SOURCE_ENTRY).should('have.length', 1)
cy.get(interact.DATA_SOURCE_ENTRY)
cy.deleteAllApps()
});
it("should generate internal table screens", () => {
cy.createTestApp()
// Create Autogenerated screens from the internal table
cy.createDatasourceScreen(["Cypress Tests"])
// Confirm screens have been auto generated
cy.get(interact.BODY).should('contain', "cypress-tests")
.and('contain', 'cypress-tests/:id')
.and('contain', 'cypress-tests/new/row')
})
it("should generate multiple internal table screens at once", () => {
const initialTable = "Cypress Tests"
const secondTable = "Table Two"
// Create a second internal table
cy.createTable(secondTable)
// Create Autogenerated screens from the internal tables
cy.createDatasourceScreen([initialTable, secondTable])
// Confirm screens have been auto generated
// Previously generated tables are suffixed with numbers - as expected
cy.wait(1000)
cy.get(interact.BODY).should('contain', 'cypress-tests-2')
.and('contain', 'cypress-tests-2/:id')
.and('contain', 'cypress-tests-2/new/row')
.and('contain', 'table-two')
.and('contain', 'table-two/:id')
.and('contain', 'table-two/new/row')
})
it("should generate multiple internal table screens with the same screen access level", () => {
//The tables created in the previous step still exist
cy.createTable("Table Three")
cy.createTable("Table Four")
cy.createDatasourceScreen(["Table Three", "Table Four"], "Admin")
// Filter screens to Admin
cy.filterScreensAccessLevel('Admin')
cy.get(interact.BODY).should('contain', 'table-three')
.and('contain', 'table-three/:id')
.and('contain', 'table-three/new/row')
.and('contain', 'table-four')
.and('contain', 'table-four/:id')
.and('contain', 'table-four/new/row')
.and('not.contain', 'table-two')
.and('not.contain', 'cypress-tests')
})
if (Cypress.env("TEST_ENV")) {
it("should generate datasource screens", () => {
// Using MySQL datasource for testing this
const datasource = "MySQL"
// Select & configure MySQL datasource
cy.selectExternalDatasource(datasource)
cy.addDatasourceConfig(datasource)
// Create Autogenerated screens from a MySQL table - MySQL contains books table
cy.createDatasourceScreen(["books"])
cy.get(interact.BODY).should('contain', 'books')
.and('contain', 'books/:id')
.and('contain', 'books/new/row')
})
}
})
})

View file

@ -1,235 +0,0 @@
import filterTests from '../support/filterTests'
const interact = require('../support/interact')
filterTests(['smoke', 'all'], () => {
context("Create an Application", () => {
before(() => {
cy.login()
cy.deleteAllApps()
})
if (!(Cypress.env("TEST_ENV"))) {
it.skip("should show the new user UI/UX", () => {
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/create`, { timeout: 5000 }) //added /portal/apps/create
cy.wait(1000)
cy.get(interact.CREATE_APP_BUTTON, { timeout: 10000 }).contains('Start from scratch').should("exist")
cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist")
cy.get(interact.TEMPLATE_CATEGORY).should("exist")
cy.get(interact.APP_TABLE).should("not.exist")
})
}
xit("should provide filterable templates", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.wait(500)
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(interact.SPECTRUM_BUTTON).contains("View Templates").click({ force: true })
}
})
cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist")
cy.get(interact.TEMPLATE_CATEGORY).should("exist")
cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).its('length').should('be.gt', 1)
cy.get(interact.TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON).its('length').should('be.gt', 2)
cy.get(interact.TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON).eq(1).click()
cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).should('have.length', 1)
cy.get(interact.TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON).eq(0).click()
cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).its('length').should('be.gt', 1)
})
it("should enforce a valid url before submission", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 10000 })
// Start create app process. If apps already exist, click second button
cy.wait(1000)
cy.get(interact.CREATE_APP_BUTTON, { timeout: 3000 }).click({ force: true })
const appName = "Cypress Tests"
cy.get(interact.SPECTRUM_MODAL).within(() => {
cy.get(interact.APP_NAME_INPUT).eq(0).should('have.focus')
//Auto fill
cy.get(interact.APP_NAME_INPUT).eq(0).clear()
cy.get(interact.APP_NAME_INPUT).eq(0).type(appName).should("have.value", appName).blur()
cy.get(interact.APP_NAME_INPUT).eq(1).should("have.value", "/cypress-tests")
cy.get(interact.SPECTRUM_BUTTON_GROUP).contains("Create app").should('not.be.disabled')
//Empty the app url - disabled create
cy.get(interact.APP_NAME_INPUT).eq(1).clear().blur()
cy.get(interact.SPECTRUM_BUTTON_GROUP).contains("Create app").should('be.disabled')
//Invalid url
cy.get(interact.APP_NAME_INPUT).eq(1).type("/new app-url").blur()
cy.get(interact.SPECTRUM_BUTTON_GROUP).contains("Create app").should('be.disabled')
//Specifically alter the url
cy.get(interact.APP_NAME_INPUT).eq(1).clear()
cy.get(interact.APP_NAME_INPUT).eq(1).type("another-app-name").blur()
cy.get(interact.APP_NAME_INPUT).eq(1).should("have.value", "/another-app-name")
cy.get(interact.APP_NAME_INPUT).eq(0).should("have.value", appName)
cy.get(interact.SPECTRUM_BUTTON_GROUP).contains("Create app").should('not.be.disabled')
})
})
it.skip("should create the first application from scratch", () => {
const appName = "Cypress Tests"
cy.createApp(appName, false)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.applicationInAppTable(appName)
cy.deleteApp(appName)
})
it.skip("should create the first application from scratch with a default name", () => {
cy.updateUserInformation("", "")
cy.createApp("", false)
cy.applicationInAppTable("My app")
cy.deleteApp("My app")
})
it("should create the first application from scratch, using the users first name as the default app name", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.updateUserInformation("Ted", "Userman")
cy.createApp("", false)
cy.applicationInAppTable("Teds app")
cy.deleteApp("Teds app")
// Accomodate names that end in 'S'
cy.updateUserInformation("Chris", "Userman")
cy.createApp("", false)
cy.applicationInAppTable("Chris app")
cy.deleteApp("Chris app")
cy.updateUserInformation("", "")
})
it("should create an application from an export", () => {
const exportedApp = 'cypress/fixtures/exported-app.txt'
cy.importApp(exportedApp, "")
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 2000 })
cy.applicationInAppTable("My app")
cy.get(".app-table .name").eq(0).click()
cy.closeModal()
cy.get(`[aria-label="ShowMenu"]`).click()
cy.get(".spectrum-Menu").within(() => {
cy.contains("Overview").click()
})
cy.get(".app-overview-actions-icon").within(() => {
cy.get(".spectrum-Icon").click({ force: true })
})
cy.get(".spectrum-Menu").contains("Delete").click({ force: true })
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get("input").type("My app")
})
cy.get(".spectrum-Button--warning").click()
})
it("should create an application from an export, using the users first name as the default app name", () => {
const exportedApp = 'cypress/fixtures/exported-app.txt'
cy.updateUserInformation("Ted", "Userman")
cy.importApp(exportedApp, "")
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.applicationInAppTable("Teds app")
cy.get(".app-table .name").eq(0).click()
cy.closeModal()
cy.get(`[aria-label="ShowMenu"]`).click()
cy.get(".spectrum-Menu").within(() => {
cy.contains("Overview").click()
})
cy.get(".app-overview-actions-icon").within(() => {
cy.get(".spectrum-Icon").click({ force: true })
})
cy.get(".spectrum-Menu").contains("Delete").click({ force: true })
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get("input").type("Teds app")
})
cy.get(".spectrum-Button--warning").click()
cy.updateUserInformation("", "")
})
xit("should generate the first application from a template", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.wait(500)
// Navigate to Create new app section if apps already exist
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(interact.CREATE_APP_BUTTON).click({ force: true })
}
})
cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist")
cy.get(interact.TEMPLATE_CATEGORY).should("exist")
// Select template
cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).eq(0).within(() => {
const card = cy.get('.template-card').eq(0).should("exist");
const cardOverlay = card.get('.template-thumbnail-action-overlay').should("exist")
cardOverlay.invoke("show")
cardOverlay.get("button").contains("Use template").should("exist").click({ force: true })
})
// CMD Create app from theme card
cy.get(".spectrum-Modal").should('be.visible')
const templateName = cy.get(".spectrum-Modal .template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = "/" + templateNameText.toLowerCase().replace(/\s+/g, "-")
cy.get(interact.SPECTRUM_MODAL_INPUT).eq(0).should("have.value", templateNameText)
cy.get(interact.SPECTRUM_MODAL_INPUT).eq(1).should("have.value", templateNameParsed)
cy.get(".spectrum-Modal .spectrum-ButtonGroup").contains("Create app").click()
cy.wait(5000)
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.wait(2000)
cy.applicationInAppTable(templateNameText)
cy.deleteApp(templateNameText)
});
})
it("should display a second application and app filtering", () => {
// Create first app
const appName = "Cypress Tests"
cy.createApp(appName)
cy.visit(`${Cypress.config().baseUrl}/builder`)
// Create second app
const secondAppName = "Second App Demo"
cy.createApp(secondAppName)
cy.visit(`${Cypress.config().baseUrl}/builder`)
//Both applications should exist and be searchable
cy.searchForApplication(appName)
cy.searchForApplication(secondAppName)
cy.deleteApp(secondAppName)
})
})
})

View file

@ -1,64 +0,0 @@
import filterTests from "../support/filterTests"
const interact = require('../support/interact')
filterTests(['smoke', 'all'], () => {
xcontext("Create a automation", () => {
before(() => {
cy.login()
cy.createTestApp()
})
xit("should create a automation", () => {
cy.createTestTableWithData()
cy.wait(2000)
cy.contains("Automate").click()
cy.get(interact.SPECTRUM_BUTTON_TEMPLATE).contains("Add automation").click({ force: true })
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
cy.get("input").type("Add Row")
cy.contains("Row Created").click({ force: true })
cy.get(interact.SPECTRUM_BUTTON_CTA, { timeout: 500 }).click()
})
// Setup trigger
cy.get(interact.SPECTRUM_PICKER_LABEL).click()
cy.wait(500)
cy.contains("dog").click()
// Create action
cy.get('[aria-label="AddCircle"]', { timeout: 2000 }).click()
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
cy.wait(1000)
cy.contains("Create Row").trigger('mouseover').click().click()
cy.get(interact.SPECTRUM_BUTTON_CTA).click()
})
cy.get(interact.SPECTRUM_PICKER_LABEL).eq(1).click()
cy.contains("dog").click()
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT)
.first()
.type("{{ trigger.row.name }}", { parseSpecialCharSequences: false })
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT)
.eq(1)
.type("11")
cy.contains("Finish and test automation").click()
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
cy.get(interact.SPECTRUM_PICKER_LABEL, { timeout: 1000 }).click()
cy.contains("dog").click()
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 })
.first()
.type("automationGoodboy")
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT)
.eq(1)
.type("11")
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT)
.eq(2)
.type("123456")
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT)
.eq(3)
.type("123456")
cy.contains("Test").click()
})
cy.contains("Data").click()
cy.contains("automationGoodboy")
})
})
})

View file

@ -1,66 +0,0 @@
import filterTests from "../support/filterTests"
filterTests(['smoke', 'all'], () => {
context("Create Bindings", () => {
before(() => {
cy.login()
cy.createTestApp()
cy.navigateToFrontend()
})
it("should add a current user binding", () => {
cy.searchAndAddComponent("Paragraph").then(componentId => {
addSettingBinding("text", ["Current User", "_id"], "Current User._id")
})
cy.deleteComponentByName("New Paragraph")
})
it("should handle an invalid binding", () => {
cy.searchAndAddComponent("Paragraph").then(componentId => {
// Cypress needs to escape curly brackets
cy.get("[data-cy=setting-text] input")
.type("{{}{{}{{} Current User._id {}}{}}")
.blur()
cy.getComponent(componentId).should("have.text", "{{{ [user].[_id] }}")
cy.deleteComponentByName("New Paragraph")
})
})
xit("should add a URL param binding", () => {
const paramName = "foo"
cy.createScreen(`/test/:${paramName}`)
cy.searchAndAddComponent("Paragraph").then(componentId => {
addSettingBinding("text", ["URL", paramName], `URL.${paramName}`)
// The builder preview pages don't have a real URL, so all we can do
// is check that we were able to bind to the property, and that the
// component exists on the page
cy.getComponent(componentId).should("have.text", "New Paragraph")
})
})
it("should add a binding with a handlebars helper", () => {
cy.searchAndAddComponent("Paragraph").then(componentId => {
// Cypress needs to escape curly brackets
cy.get("[data-cy=setting-text] input")
.type("{{}{{} add 1 2 {}}{}}")
.blur()
cy.getComponent(componentId).should("have.text", "3")
})
})
})
const addSettingBinding = (setting, bindingCategories, bindingText, clickOption = true) => {
cy.get(`[data-cy="setting-${setting}"] [data-cy=text-binding-button]`).click()
cy.get(".category-list li").contains(bindingCategories[0])
cy.get(".drawer").within(() => {
if (clickOption) {
cy.get(".category-list li").contains(bindingCategories[0]).click()
cy.get("li.binding").contains(bindingCategories[1]).click()
cy.get("textarea").should("have.value", `{{ ${bindingText} }}`)
} else {
cy.get("textarea").type(bindingText)
}
cy.contains("Save").click()
})
}
})

View file

@ -1,275 +0,0 @@
import filterTests from "../support/filterTests"
const interact = require("../support/interact")
filterTests(["all"], () => {
xcontext("Create Components", () => {
let headlineId
before(() => {
cy.login()
cy.createTestApp()
cy.createTable("dog")
cy.addColumn("dog", "name", "Text")
cy.addColumn("dog", "age", "Number")
cy.addColumn("dog", "breed", "Options")
cy.navigateToFrontend()
cy.wait(1000) //allow the iframe some wiggle room
})
//Use the tree to delete a selected component
const deleteSelectedComponent = () => {
cy.get(
".nav-item.selected .actions > div > .icon"
).click({
force: true,
})
cy.get(".spectrum-Popover.is-open li").contains("Delete").click()
cy.get(".spectrum-Modal button").contains("Delete Component").click({
force: true,
})
}
it("should add a container", () => {
cy.searchAndAddComponent("Container").then(componentId => {
cy.getComponent(componentId).should("exist")
})
})
it("should add a headline", () => {
cy.searchAndAddComponent("Headline").then(componentId => {
headlineId = componentId
cy.getComponent(headlineId).should("exist")
})
})
it("should change the text of the headline", () => {
const text = "Lorem ipsum dolor sit amet."
cy.get("[data-cy=setting-text] input").type(text).blur()
cy.getComponent(headlineId).should("have.text", text)
})
it("should change the size of the headline", () => {
cy.get("[data-cy=setting-size]").scrollIntoView().click()
cy.get("[data-cy=setting-size]").within(() => {
cy.get(".spectrum-Form-item li.spectrum-Menu-item")
.contains("3XL")
.click()
})
cy.getComponent(headlineId).within(() => {
cy.get(".spectrum-Heading").should("have.css", "font-size", "60px")
})
})
it("should create a form and reset to match schema", () => {
cy.searchAndAddComponent("Form").then(() => {
cy.get("[data-cy=setting-dataSource]").contains("Custom").click()
cy.get(interact.DROPDOWN).contains("dog").click()
cy.wait(500)
cy.searchAndAddComponent("Field Group").then(fieldGroupId => {
cy.contains("Update form fields").click()
cy.get(".spectrum-Modal")
.get(".confirm-wrap .spectrum-Button")
.click()
cy.wait(500)
cy.getComponent(fieldGroupId).within(() => {
cy.contains("name").should("exist")
cy.contains("age").should("exist")
cy.contains("breed").should("exist")
// cy.contains("image").should("exist")
})
cy.getComponent(fieldGroupId).find("input").should("have.length", 2)
cy.getComponent(fieldGroupId)
.find(interact.SPECTRUM_PICKER)
.should("have.length", 1)
})
})
})
it("deletes a component", () => {
cy.searchAndAddComponent("Paragraph").then(componentId => {
cy.get("[data-cy=setting-_instanceName] input").type(componentId).blur()
cy.get(
".nav-item.selected .actions > div > .icon"
).click({
force: true,
})
cy.get(".spectrum-Popover.is-open li").contains("Delete").click()
cy.get(".spectrum-Modal button").contains("Delete Component").click({
force: true,
})
cy.getComponent(componentId).should("not.exist")
})
})
it("should clear the iframe place holder when a form field has been set", () => {
cy.searchAndAddComponent("Form").then(formId => {
//For deletion
cy.get("[data-cy=setting-_instanceName] input")
.clear()
.type(formId)
.blur()
cy.get("[data-cy=setting-dataSource]").contains("Custom").click()
cy.get(".dropdown").contains("dog").click()
const fieldTypeToColumnName = {
"Text Field": "name",
"Number Field": "age",
"Options Picker": "breed",
}
const componentTypeLabels = Object.keys(fieldTypeToColumnName)
const testFieldFocusOnCreate = componentLabel => {
cy.log("Adding: " + componentLabel)
return cy.searchAndAddComponent(componentLabel).then(componentId => {
cy.get("[data-cy=setting-field] button.spectrum-Picker").click()
//Click the first appropriate field. They are filtered by type
cy.get(
"[data-cy=setting-field] .spectrum-Popover.is-open li.spectrum-Menu-item"
)
.contains(fieldTypeToColumnName[componentLabel])
.click()
cy.wait(500)
cy.getComponent(componentId)
.find(".component-placeholder")
.should("not.exist")
})
}
cy.wait(500)
cy.wrap(componentTypeLabels)
.each(label => {
return testFieldFocusOnCreate(label)
})
.then(() => {
cy.get(".nav-item")
.contains(formId)
.click({ force: true })
deleteSelectedComponent()
})
})
})
it("should populate the provider for charts with a data provider in its path", () => {
cy.searchAndAddComponent("Data Provider").then(providerId => {
//For deletion
cy.get("[data-cy=setting-_instanceName] input")
.clear()
.type(providerId)
.blur()
cy.get("[data-cy=setting-dataSource]")
.contains("Choose an option")
.click()
cy.get(`[data-cy=dataSource-popover-${providerId}] ul li`)
.contains("dog")
.click()
const chartTypeLabels = [
"Bar Chart",
"Line Chart",
"Area Chart",
"Pie Chart",
"Donut Chart",
"Candlestick Chart",
]
const testFocusOnCreate = chartLabel => {
cy.log("Adding: " + chartLabel)
cy.searchAndAddComponent(chartLabel).then(componentId => {
cy.get(
"[data-cy=dataProvider-prop-control] .spectrum-Picker"
).should("not.have.class", "is-focused")
// Pre populated.
cy.get("[data-cy=dataProvider-prop-control] .spectrum-Picker-label")
.contains(providerId)
.should("exist")
})
}
cy.wait(1000)
cy.wrap(chartTypeLabels)
.each(label => {
return testFocusOnCreate(label)
})
.then(() => {
cy.get(".nav-item")
.contains(providerId)
.click({ force: true })
deleteSelectedComponent()
})
})
})
it("should replace the placeholder when a url is set on an image", () => {
cy.searchAndAddComponent("Image").then(imageId => {
cy.get("[data-cy=setting-_instanceName] input")
.clear()
.type(imageId)
.blur()
//return $("New Data Provider.Rows")[0]["Attachment"][0]["url"]
//No minio, so just enter something local that will not reslove
cy.get("[data-cy=url-prop-control] input[type=text]")
.type("cypress/fixtures/ghost.png")
.blur()
cy.getComponent(imageId)
.find(".component-placeholder")
.should("not.exist")
cy.getComponent(imageId).find(`img[alt=${imageId}]`).should("exist")
cy.get(".nav-item")
.contains(imageId)
.click({ force: true })
deleteSelectedComponent()
})
})
it("should add a markdown component.", () => {
cy.searchAndAddComponent("Markdown Viewer").then(markdownId => {
cy.get("[data-cy=setting-_instanceName] input")
.clear()
.type(markdownId)
.blur()
cy.get(
"[data-cy=value-prop-control] input[type=text].spectrum-Textfield-input"
)
.type("# Hi")
.blur()
cy.getComponent(markdownId)
.find(".component-placeholder")
.should("not.exist")
cy.getComponent(markdownId)
.find(".editor-preview-full h1")
.contains("Hi")
cy.get(".nav-item")
.contains(markdownId)
.click({ force: true })
deleteSelectedComponent()
})
})
it("should direct the user when adding an Icon component.", () => {
cy.searchAndAddComponent("Icon").then(iconId => {
cy.get("[data-cy=setting-_instanceName] input")
.clear()
.type(iconId)
.blur()
cy.get("[data-cy=icon-prop-control] .spectrum-ActionButton").click()
cy.get("[data-cy=icon-popover].spectrum-Popover.is-open").within(() => {
cy.get(".search-input input").type("save").blur()
cy.get(".search-input button").click({ force: true })
cy.get(".icon-area .icon-container").eq(0).click({ force: true })
})
cy.getComponent(iconId)
.find(".component-placeholder")
.should("not.exist")
cy.getComponent(iconId).find("i.ri-save-fill").should("exist")
cy.get(".nav-item")
.contains(iconId)
.click({ force: true })
deleteSelectedComponent()
})
})
})
})

View file

@ -1,54 +0,0 @@
import filterTests from "../support/filterTests"
const interact = require('../support/interact')
filterTests(["smoke", "all"], () => {
xcontext("Screen Tests", () => {
before(() => {
cy.login()
cy.createTestApp()
cy.navigateToFrontend()
})
it.skip("Should successfully create a screen", () => {
cy.createScreen("test")
cy.get(interact.BODY).within(() => {
cy.contains("/test").should("exist")
})
})
it("Should update the url", () => {
cy.createScreen("test with spaces")
cy.get(interact.BODY).within(() => {
cy.contains("/test-with-spaces").should("exist")
})
})
it.skip("should delete all screens then create first screen via button", () => {
cy.deleteAllScreens()
cy.contains("Create first screen").click()
cy.get(interact.BODY, { timeout: 2000 }).should('contain', '/home')
})
it("Should create and filter screens by access level", () => {
const accessLevels = ["Basic", "Admin", "Public", "Power"]
for (const access of accessLevels) {
// Create screen with specified access level
cy.createScreen(access, access)
// Filter by access level and confirm screen visible
cy.filterScreensAccessLevel(access)
cy.get(interact.BODY).within(() => {
cy.get(interact.NAV_ITEM).should('contain', access.toLowerCase())
})
}
// Filter by All screens - Confirm all screens visible
cy.filterScreensAccessLevel("All screens")
cy.get(interact.BODY).should('contain', accessLevels[0])
.and('contain', accessLevels[1])
.and('contain', accessLevels[2])
.and('contain', accessLevels[3])
})
})
})

View file

@ -1,112 +0,0 @@
import filterTests from "../support/filterTests"
const interact = require('../support/interact')
filterTests(["smoke", "all"], () => {
xcontext("Create a Table", () => {
before(() => {
cy.login()
cy.createTestApp()
})
it("should create a new Table", () => {
cy.createTable("dog")
// Check if Table exists
cy.get(interact.TABLE_TITLE_H1, { timeout: 1000 }).should("have.text", "dog")
})
it("adds a new column to the table", () => {
cy.addColumn("dog", "name", "Text")
cy.contains("name").should("be.visible")
})
it("creates a row in the table", () => {
cy.addRow(["Rover"])
cy.contains("Rover").should("be.visible")
})
it("updates a column on the table", () => {
cy.get(interact.TABLE_TITLE).click()
cy.get(interact.SPECTRUM_TABLE_EDIT).click()
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
cy.get("input").eq(0).type("updated", { force: true })
// Unset table display column
cy.get(interact.SPECTRUM_SWITCH_INPUT).eq(1).click()
cy.contains("Save Column").click()
})
cy.contains("nameupdated ").should("contain", "nameupdated")
})
it("edits a row", () => {
cy.contains("button", "Edit").click({ force: true })
cy.wait(500)
cy.get(interact.SPECTRUM_MODAL_INPUT).clear()
cy.get(interact.SPECTRUM_MODAL_INPUT).type("Updated")
cy.contains("Save").click()
cy.contains("Updated").should("have.text", "Updated")
})
it("deletes a row", () => {
cy.get(interact.SPECTRUM_CHECKBOX_INPUT).check({ force: true })
cy.contains("Delete 1 row").click()
cy.get(interact.SPECTRUM_MODAL).contains("Delete").click()
cy.contains("RoverUpdated").should("not.exist")
})
if (Cypress.env("TEST_ENV")) {
// No Pagination in CI - Test env only for the next two tests
xit("Adds 15 rows and checks pagination", () => {
// 10 rows per page, 15 rows should create 2 pages within table
const totalRows = 16
for (let i = 1; i < totalRows; i++) {
cy.addRow([i])
}
cy.reload()
cy.get(interact.SPECTRUM_PAGINATION, { timeout: 2000 }).within(() => {
cy.get(interact.SPECTRUM_ACTION_BUTTON).eq(1).click()
})
cy.get(interact.SPECTRUM_PAGINATION).within(() => {
cy.get(interact.SPECTRUM_BODY_SECOND).contains("Page 2")
})
})
xit("Deletes rows and checks pagination", () => {
// Delete rows, removing second page from table
cy.get(interact.SPECTRUM_CHECKBOX_INPUT).check({ force: true })
cy.get(interact.POPOVERS).within(() => {
cy.get(interact.SPECTRUM_BUTTON).click({ force: true })
})
cy.get(interact.SPECTRUM_DIALOG_GRID).contains("Delete").click({ force: true })
// Confirm table only has one page
cy.get(interact.SPECTRUM_PAGINATION, { timeout: 1000 }).within(() => {
cy.get(interact.SPECTRUM_ACTION_BUTTON).eq(1).should("not.be.enabled")
})
})
}
it("deletes a column", () => {
const columnName = "nameupdated"
cy.get(interact.TABLE_TITLE).click()
cy.get(interact.SPECTRUM_TABLE_EDIT).click()
cy.contains("Delete").click()
cy.get(interact.DELETE_COLUMN_CONFIRM).type(columnName)
cy.contains("Delete Column").click()
cy.contains("nameupdated").should("not.exist")
})
it("deletes a table", () => {
cy.get(interact.NAV_ITEM)
.contains("dog")
.parents(interact.NAV_ITEM)
.first()
.within(() => {
cy.get(interact.ACTION_SPECTRUM_ICON).click({ force: true })
})
cy.get(interact.SPECTRUM_MENU_CHILD2).click()
cy.get(interact.DELETE_TABLE_CONFIRM).type("dog")
cy.contains("Delete Table").click()
cy.contains("dog").should("not.exist")
})
})
})

View file

@ -1,161 +0,0 @@
import filterTests from "../support/filterTests"
const interact = require('../support/interact')
filterTests(['smoke', 'all'], () => {
xcontext("Create a View", () => {
before(() => {
cy.login()
cy.createTestApp()
cy.createTable("data")
cy.addColumn("data", "group", "Text")
cy.addColumn("data", "age", "Number")
cy.addColumn("data", "rating", "Number")
// 6 Rows
cy.addRow(["Students", 25, 1])
cy.addRow(["Students", 20, 3])
cy.addRow(["Students", 18, 6])
cy.addRow(["Students", 25, 2])
cy.addRow(["Teachers", 49, 5])
cy.addRow(["Teachers", 36, 3])
})
xit("creates a view", () => {
cy.contains("Create view").click()
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
cy.get("input").type("Test View")
cy.get("button").contains("Create View").click({ force: true })
})
cy.contains(interact.TABLE_TITLE_H1, "Test View", { timeout: 10000 })
cy.get(".table-wrapper").within(() => {
cy.get(interact.TITLE).then($headers => {
expect($headers).to.have.length(3)
const headers = Array.from($headers).map(header =>
header.textContent.trim()
)
expect(removeSpacing(headers)).to.deep.eq(["group", "age", "rating"])
})
})
})
xit("filters the view by age over 10", () => {
cy.contains("Filter").click()
cy.contains("Add Filter").click()
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
cy.get(interact.SPECTRUM_PICKER_LABEL).eq(0).click()
cy.contains("age").click({ force: true })
cy.get(interact.SPECTRUM_PICKER_LABEL).eq(1).click()
cy.contains("More Than").click({ force: true })
cy.get("input").type(18)
cy.contains("Save").click()
})
cy.get(interact.SPECTRUM_TABLE_ROW).get($values => {
expect($values).to.have.length(5)
})
})
xit("creates a stats calculation view based on age", () => {
cy.wait(1000)
cy.contains("Calculate").click()
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
cy.get(interact.SPECTRUM_PICKER_LABEL).eq(0).click()
cy.contains("Statistics").click()
cy.get(interact.SPECTRUM_PICKER_LABEL).eq(1).click()
cy.contains("age").click({ force: true })
cy.get(interact.SPECTRUM_BUTTON).contains("Save").click({ force: true })
})
cy.wait(1000)
cy.get(".table-wrapper").within(() => {
cy.get(interact.TITLE).then($headers => {
expect($headers).to.have.length(7)
const headers = Array.from($headers).map(header =>
header.textContent.trim()
)
expect(removeSpacing(headers)).to.deep.eq([
"field",
"sum",
"min",
"max",
"count",
"sumsqr",
"avg",
])
})
})
cy.get(interact.SPECTRUM_TABLE_CELL).then($values => {
let values = Array.from($values).map(header => header.textContent.trim())
expect(values).to.deep.eq(["age", "155", "20", "49", "5", "5347", "31"])
})
})
xit("groups the view by group", () => {
cy.contains("Group by").click()
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
cy.get(interact.SPECTRUM_PICKER_LABEL).eq(0).click()
cy.contains("group").click()
cy.contains("Save").click()
})
cy.wait(1000)
cy.contains("Students").should("be.visible")
cy.contains("Teachers").should("be.visible")
cy.get(interact.SPECTRUM_TABLE_CELL).then($values => {
let values = Array.from($values).map(header => header.textContent.trim())
expect(values).to.deep.eq([
"Students",
"70",
"20",
"25",
"3",
"1650",
"23.333333333333332",
"Teachers",
"85",
"36",
"49",
"2",
"3697",
"42.5",
])
})
})
xit("renames a view", () => {
cy.contains(interact.NAV_ITEM, "Test View")
.find(".actions .icon.open-popover")
.click({ force: true })
cy.get(interact.SPECTRUM_MENU_ITEM_LABEL).contains("Edit").click()
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
cy.get("input").type(" Updated")
cy.contains("Save").click()
})
cy.wait(1000)
cy.contains("Test View Updated").should("be.visible")
})
it("deletes a view", () => {
cy.contains(interact.NAV_ITEM, "Test View Updated")
.find(".actions .icon.open-popover")
.click({ force: true })
cy.contains("Delete").click()
cy.contains("Delete View").click()
cy.wait(500)
cy.contains("TestView Updated").should("not.exist")
})
})
function removeSpacing(headers) {
let newHeaders = []
for (let header of headers) {
newHeaders.push(header.replace(/\s\s+/g, " "))
}
return newHeaders
}
})

View file

@ -1,86 +0,0 @@
import filterTests from "../support/filterTests"
filterTests(['all'], () => {
xcontext("Custom Theming Properties", () => {
before(() => {
cy.login()
cy.createTestApp()
cy.navigateToFrontend()
})
/* Default Values:
Button roundness = Large
Accent colour = Blue 600
Accent colour (hover) = Blue 500
Navigation bar background colour = Gray 100
Navigation bar text colour = Gray 800 */
it("should reset the color property values", () => {
// Open Theme modal and change colours
cy.get(".spectrum-ActionButton-label").contains("Theme").click()
cy.get(".spectrum-Picker").contains("Large").click()
.parents()
.get(".spectrum-Menu-itemLabel").contains("None").click()
changeThemeColors()
// Reset colours
cy.get(".spectrum-Button-label").contains("Reset").click({force: true})
// Check values have reset
checkThemeColorDefaults()
})
/* Button Roundness Values:
None = 0
Small = 4px
Medium = 8px
Large = 16px */
it("should test button roundness", () => {
const buttonRoundnessValues = ["0", "4px", "8px", "16px"]
// Add button, change roundness and confirm value
cy.addComponent("Button", null).then((componentId) => {
buttonRoundnessValues.forEach(function (item, index){
cy.get(".spectrum-ActionButton-label").contains("Theme").click()
cy.get(".setting").contains("Button roundness").parent()
.get(".select-wrapper").click()
cy.get(".spectrum-Popover").find('li').eq(index).click()
cy.get(".spectrum-Button").contains("View changes").click({force: true})
cy.reload()
cy.getComponent(componentId)
.parents(".svelte-xiqd1c").eq(0).should('have.attr', 'style').and('contains', `--buttonBorderRadius:${item}`)
})
})
})
const changeThemeColors = () => {
// Changes the theme colours
cy.get(".spectrum-FieldLabel").contains("Accent color")
.parent().find(".container.svelte-z3cm5a").click()
.find('[title="Red 400"]').click()
cy.get(".spectrum-FieldLabel").contains("Accent color (hover)")
.parent().find(".container.svelte-z3cm5a").click()
.find('[title="Orange 400"]').click()
cy.get(".spectrum-FieldLabel").contains("Navigation bar background color")
.parent().find(".container.svelte-z3cm5a").click()
.find('[title="Yellow 400"]').click()
cy.get(".spectrum-FieldLabel").contains("Navigation bar text color")
.parent().find(".container.svelte-z3cm5a").click()
.find('[title="Green 400"]').click()
}
const checkThemeColorDefaults = () => {
cy.get(".spectrum-FieldLabel").contains("Accent color")
.parent().find(".container.svelte-z3cm5a").click()
.get('[title="Blue 600"]').children().find('[aria-label="Checkmark"]')
cy.get(".spectrum-Dialog-grid").click()
cy.get(".spectrum-FieldLabel").contains("Accent color (hover)")
.parent().find(".container.svelte-z3cm5a").click()
.get('[title="Blue 500"]').children().find('[aria-label="Checkmark"]')
cy.get(".spectrum-Dialog-grid").click()
cy.get(".spectrum-FieldLabel").contains("Navigation bar background color")
.parent().find(".container.svelte-z3cm5a").click()
.get('[title="Gray 100"]').children().find('[aria-label="Checkmark"]')
cy.get(".spectrum-Dialog-grid").click()
cy.get(".spectrum-FieldLabel").contains("Navigation bar text color")
.parent().find(".container.svelte-z3cm5a").click()
.get('[title="Gray 800"]').children().find('[aria-label="Checkmark"]')
}
})
})

View file

@ -1,42 +0,0 @@
import filterTests from "../../support/filterTests"
filterTests(['all'], () => {
xcontext("Datasource Wizard", () => {
if (Cypress.env("TEST_ENV")) {
before(() => {
cy.login()
cy.createTestApp()
})
it("should navigate in and out of a datasource via wizard", () => {
// Select PostgreSQL and add config (without fetch)
const datasource = "Oracle"
cy.selectExternalDatasource(datasource)
cy.addDatasourceConfig(datasource, true)
// Navigate back within datasource wizard
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button").contains("Back").click({ force: true })
})
// Select PostgreSQL datasource again
cy.get(".item-list", { timeout: 1000 }).contains(datasource).click()
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
})
// Fetch tables after selection
// Previously entered config should not have been saved
// Config is back to default values
// Modal will close and provide 500 error
cy.intercept('**/datasources').as('datasourceConnection')
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button").contains("Save and fetch tables").click({ force: true })
})
cy.wait("@datasourceConnection")
cy.get("@datasourceConnection").its('response.body')
.should('have.property', 'status', 500)
})
}
})
})

View file

@ -1,229 +0,0 @@
import filterTests from "../../support/filterTests"
filterTests(["all"], () => {
xcontext("MySQL Datasource Testing", () => {
if (Cypress.env("TEST_ENV")) {
before(() => {
cy.login()
cy.createTestApp()
})
const datasource = "MySQL"
const queryName = "Cypress Test Query"
const queryRename = "CT Query Rename"
it("Should add MySQL datasource without configuration", () => {
// Select MySQL datasource
cy.selectExternalDatasource(datasource)
// Attempt to fetch tables without applying configuration
cy.intercept("**/datasources").as("datasource")
cy.get(".spectrum-Button")
.contains("Save and fetch tables")
.click({ force: true })
cy.wait(500)
// Intercept Request after button click & apply assertions
cy.wait("@datasource")
cy.get("@datasource")
.its("response.body")
.should(
"have.property",
"message",
"connect ECONNREFUSED 127.0.0.1:3306"
)
cy.get("@datasource")
.its("response.body")
.should("have.property", "status", 500)
cy.get(".spectrum-Button").contains("Skip table fetch").click({ force: true })
})
it("should add MySQL datasource and fetch tables", () => {
// Add & configure MySQL datasource
cy.selectExternalDatasource(datasource)
cy.intercept("**/datasources").as("datasource")
cy.addDatasourceConfig(datasource)
// Check response from datasource after adding configuration
cy.wait("@datasource")
cy.get("@datasource").its("response.statusCode").should("eq", 200)
// Confirm fetch tables was successful
cy.get(".spectrum-Table")
.eq(0)
.find(".spectrum-Table-row")
.its("length")
.should("be.gt", 0)
})
it("should check table fetching error", () => {
// MySQL test datasource contains tables without primary keys
cy.get(".spectrum-InLineAlert")
.should("contain", "Error fetching tables")
.and("contain", "No primary key constraint found")
})
it("should define a One relationship type", () => {
// Select relationship type & configure
cy.get(".spectrum-Button")
.contains("Define relationship")
.click({ force: true })
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Picker").eq(0).click()
cy.get(".spectrum-Popover").contains("One").click()
cy.get(".spectrum-Picker").eq(1).click()
cy.get(".spectrum-Popover").contains("REGIONS").click()
cy.get(".spectrum-Picker").eq(2).click()
cy.get(".spectrum-Popover").contains("REGION_ID").click()
cy.get(".spectrum-Picker").eq(3).click()
cy.get(".spectrum-Popover").contains("COUNTRIES").click()
cy.get(".spectrum-Picker").eq(4).click()
cy.get(".spectrum-Popover").contains("REGION_ID").click()
})
// Save relationship & reload page
cy.get(".spectrum-ButtonGroup").within(() => {
cy.get(".spectrum-Button").contains("Save").click({ force: true })
})
cy.reload()
// Confirm table length & column name
cy.get(".spectrum-Table")
.eq(1)
.find(".spectrum-Table-row")
.its("length")
.should("eq", 1)
cy.get(".spectrum-Table-cell").should("contain", "COUNTRIES to REGIONS")
})
it("should define a Many relationship type", () => {
// Select relationship type & configure
cy.get(".spectrum-Button")
.contains("Define relationship")
.click({ force: true })
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Picker").eq(0).click()
cy.get(".spectrum-Popover").contains("Many").click()
cy.get(".spectrum-Picker").eq(1).click()
cy.get(".spectrum-Popover").contains("LOCATIONS").click()
cy.get(".spectrum-Picker").eq(2).click()
cy.get(".spectrum-Popover").contains("REGIONS").click()
cy.get(".spectrum-Picker").eq(3).click()
cy.get(".spectrum-Popover").contains("COUNTRIES").click()
cy.get(".spectrum-Picker").eq(4).click()
cy.get(".spectrum-Popover").contains("COUNTRY_ID").click()
cy.get(".spectrum-Picker").eq(5).click()
cy.get(".spectrum-Popover").contains("REGION_ID").click()
// Save relationship & reload page
cy.get(".spectrum-Button").contains("Save").click({ force: true })
cy.reload()
cy.wait(1000)
})
// Confirm table length & relationship name
cy.get(".spectrum-Table", { timeout: 1000 })
.eq(1)
.find(".spectrum-Table-row")
.its("length")
.should("eq", 2)
cy.get(".spectrum-Table-cell").should(
"contain",
"LOCATIONS through COUNTRIES → REGIONS"
)
})
it("should delete relationships", () => {
// Delete both relationships
cy.get(".spectrum-Table")
.eq(1)
.find(".spectrum-Table-row")
.its("length")
.then(len => {
for (let i = 0; i < len; i++) {
cy.get(".spectrum-Table")
.eq(1)
.within(() => {
cy.get(".spectrum-Table-cell").eq(0).click({ force: true })
})
cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => {
cy.get(".spectrum-Button")
.contains("Delete")
.click({ force: true })
})
cy.reload()
cy.wait(500)
}
// Confirm relationships no longer exist
cy.get(".spectrum-Body").should(
"contain",
"No relationships configured"
)
})
})
it("should add a query", () => {
// Add query
cy.get(".spectrum-Button").contains("Add query").click({ force: true })
cy.get(".spectrum-Form-item")
.eq(0)
.within(() => {
cy.get("input").type(queryName)
})
// Insert Query within Fields section
cy.get(".CodeMirror textarea")
.eq(0)
.type("SELECT * FROM books", { force: true })
// Intercept query execution
cy.intercept("**/queries/preview").as("query")
cy.get(".spectrum-Button").contains("Run Query").click({ force: true })
cy.wait(500)
cy.wait("@query")
// Assert against Status Code & Body
cy.get("@query").its("response.statusCode").should("eq", 200)
cy.get("@query").its("response.body").should("not.be.empty")
// Save query
cy.intercept("POST", "**/queries").as("saveQuery")
cy.get(".spectrum-Button").contains("Save Query").click({ force: true })
cy.wait("@saveQuery")
cy.get("@saveQuery").its("response.statusCode").should("eq", 200)
cy.get(".nav-item").should("contain", queryName)
})
it("should duplicate a query", () => {
/// Get query nav item - QueryName
cy.get(".nav-item")
.contains(queryName)
.parent()
.within(() => {
cy.get(".spectrum-Icon").eq(1).click({ force: true })
})
// Select and confirm duplication
cy.get(".spectrum-Menu").contains("Duplicate").click()
cy.get(".nav-item").should("contain", queryName + " (1)")
})
it("should edit a query name", () => {
// Rename query
cy.get(".spectrum-Form-item")
.eq(0)
.within(() => {
cy.get("input").clear().type(queryRename)
})
// Click on a nav item
cy.get(".nav-item").first().click()
// Confirm name change
cy.get(".nav-item").should("contain", queryRename)
})
it("should delete a query", () => {
// Get query nav item - QueryName
cy.get(".nav-item")
.contains(queryRename)
.parent()
.within(() => {
cy.get(".spectrum-Icon").eq(1).click({ force: true })
})
// Select Delete
cy.get(".spectrum-Menu").contains("Delete").click()
cy.get(".spectrum-Button")
.contains("Delete Query")
.click({ force: true })
// Confirm deletion
cy.get(".nav-item", { timeout: 1000 }).should("not.contain", queryRename)
})
}
})
})

View file

@ -1,229 +0,0 @@
import filterTests from "../../support/filterTests"
filterTests(["all"], () => {
xcontext("Oracle Datasource Testing", () => {
if (Cypress.env("TEST_ENV")) {
before(() => {
cy.login()
cy.createTestApp()
})
const datasource = "Oracle"
const queryName = "Cypress Test Query"
const queryRename = "CT Query Rename"
it("Should add Oracle datasource and skip table fetch", () => {
// Select Oracle datasource
cy.selectExternalDatasource(datasource)
// Skip table fetch - no config added
cy.get(".spectrum-Button")
.contains("Skip table fetch")
.click({ force: true })
cy.wait(500)
// Confirm config contains localhost
cy.get(".spectrum-Textfield-input", { timeout: 500 })
.eq(1)
.should("have.value", "localhost")
// Add another Oracle datasource, configure & skip table fetch
cy.selectExternalDatasource(datasource)
cy.addDatasourceConfig(datasource, true)
// Confirm config and no tables
cy.get(".spectrum-Textfield-input")
.eq(1)
.should("have.value", Cypress.env("oracle").HOST)
cy.get(".spectrum-Body").eq(2).should("contain", "No tables found.")
})
it("Should add Oracle datasource and fetch tables without configuration", () => {
// Select Oracle datasource
cy.selectExternalDatasource(datasource)
// Attempt to fetch tables without applying configuration
cy.intercept("**/datasources").as("datasource")
cy.get(".spectrum-Button")
.contains("Save and fetch tables")
.click({ force: true })
// Intercept Request after button click & apply assertions
cy.wait("@datasource")
cy.get("@datasource")
.its("response.body")
.should("have.property", "status", 500)
cy.get(".spectrum-Button").contains("Skip table fetch").click({ force: true })
})
xit("should add Oracle datasource and fetch tables", () => {
// Add & configure Oracle datasource
cy.selectExternalDatasource(datasource)
cy.intercept("**/datasources").as("datasource")
cy.addDatasourceConfig(datasource)
// Check response from datasource after adding configuration
cy.wait("@datasource")
cy.get("@datasource").its("response.statusCode").should("eq", 200)
// Confirm fetch tables was successful
cy.get(".spectrum-Table")
.eq(0)
.find(".spectrum-Table-row")
.its("length")
.should("be.gt", 0)
})
xit("should define a One relationship type", () => {
// Select relationship type & configure
cy.get(".spectrum-Button")
.contains("Define relationship")
.click({ force: true })
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Picker").eq(0).click()
cy.get(".spectrum-Popover").contains("One").click()
cy.get(".spectrum-Picker").eq(1).click()
cy.get(".spectrum-Popover").contains("REGIONS").click()
cy.get(".spectrum-Picker").eq(2).click()
cy.get(".spectrum-Popover").contains("REGION_ID").click()
cy.get(".spectrum-Picker").eq(3).click()
cy.get(".spectrum-Popover").contains("COUNTRIES").click()
cy.get(".spectrum-Picker").eq(4).click()
cy.get(".spectrum-Popover").contains("REGION_ID").click()
// Save relationship & reload page
cy.get(".spectrum-Button").contains("Save").click({ force: true })
cy.reload()
})
// Confirm table length & column name
cy.get(".spectrum-Table")
.eq(1)
.find(".spectrum-Table-row")
.its("length")
.should("eq", 1)
cy.get(".spectrum-Table-cell").should("contain", "COUNTRIES to REGIONS")
})
xit("should define a Many relationship type", () => {
// Select relationship type & configure
cy.get(".spectrum-Button")
.contains("Define relationship")
.click({ force: true })
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Picker").eq(0).click()
cy.get(".spectrum-Popover").contains("Many").click()
cy.get(".spectrum-Picker").eq(1).click()
cy.get(".spectrum-Popover").contains("LOCATIONS").click()
cy.get(".spectrum-Picker").eq(2).click()
cy.get(".spectrum-Popover").contains("REGIONS").click()
cy.get(".spectrum-Picker").eq(3).click()
cy.get(".spectrum-Popover").contains("COUNTRIES").click()
cy.get(".spectrum-Picker").eq(4).click()
cy.get(".spectrum-Popover").contains("COUNTRY_ID").click()
cy.get(".spectrum-Picker").eq(5).click()
cy.get(".spectrum-Popover").contains("REGION_ID").click()
// Save relationship & reload page
cy.get(".spectrum-Button").contains("Save").click({ force: true })
cy.reload()
})
// Confirm table length & relationship name
cy.get(".spectrum-Table")
.eq(1)
.find(".spectrum-Table-row")
.its("length")
.should("eq", 2)
cy.get(".spectrum-Table-cell").should(
"contain",
"LOCATIONS through COUNTRIES → REGIONS"
)
})
xit("should delete relationships", () => {
// Delete both relationships
cy.get(".spectrum-Table")
.eq(1)
.find(".spectrum-Table-row")
.its("length")
.then(len => {
for (let i = 0; i < len; i++) {
cy.get(".spectrum-Table")
.eq(1)
.within(() => {
cy.get(".spectrum-Table-row").eq(0).click()
})
cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => {
cy.get(".spectrum-Button")
.contains("Delete")
.click({ force: true })
})
cy.reload()
}
// Confirm relationships no longer exist
cy.get(".spectrum-Body").should(
"contain",
"No relationships configured"
)
})
})
xit("should add a query", () => {
// Add query
cy.get(".spectrum-Button").contains("Add query").click({ force: true })
cy.get(".spectrum-Form-item")
.eq(0)
.within(() => {
cy.get("input").type(queryName)
})
// Insert Query within Fields section
cy.get(".CodeMirror textarea")
.eq(0)
.type("SELECT * FROM JOBS", { force: true })
// Intercept query execution
cy.intercept("**/queries/preview").as("query")
cy.get(".spectrum-Button").contains("Run Query").click({ force: true })
cy.wait(500)
cy.wait("@query")
// Assert against Status Code & Body
cy.get("@query").its("response.statusCode").should("eq", 200)
cy.get("@query").its("response.body").should("not.be.empty")
// Save query
cy.get(".spectrum-Button").contains("Save Query").click({ force: true })
cy.get(".nav-item").should("contain", queryName)
})
xit("should duplicate a query", () => {
// Get query nav item
cy.get(".nav-item")
.contains(queryName)
.parent()
.within(() => {
cy.get(".spectrum-Icon").eq(1).click({ force: true })
})
// Select and confirm duplication
cy.get(".spectrum-Menu").contains("Duplicate").click()
cy.get(".nav-item").should("contain", queryName + " (1)")
})
xit("should edit a query name", () => {
// Rename query
cy.get(".spectrum-Form-item")
.eq(0)
.within(() => {
cy.get("input").clear().type(queryRename)
})
// Save query
cy.get(".spectrum-Button").contains("Save Query").click({ force: true })
cy.get(".nav-item").should("contain", queryRename)
})
xit("should delete a query", () => {
// Get query nav item - QueryName
cy.get(".nav-item")
.contains(queryName)
.parent()
.within(() => {
cy.get(".spectrum-Icon").eq(1).click({ force: true })
})
// Select Delete
cy.get(".spectrum-Menu").contains("Delete").click()
cy.get(".spectrum-Button")
.contains("Delete Query")
.click({ force: true })
// Confirm deletion
cy.get(".nav-item", { timeout: 1000 }).should("not.contain", queryName)
})
}
})
})

View file

@ -1,283 +0,0 @@
import filterTests from "../../support/filterTests"
filterTests(["all"], () => {
xcontext("PostgreSQL Datasource Testing", () => {
if (Cypress.env("TEST_ENV")) {
before(() => {
cy.login()
cy.createTestApp()
})
const datasource = "PostgreSQL"
const queryName = "Cypress Test Query"
const queryRename = "CT Query Rename"
xit("Should add PostgreSQL datasource without configuration", () => {
// Select PostgreSQL datasource
cy.selectExternalDatasource(datasource)
// Attempt to fetch tables without applying configuration
cy.intercept("**/datasources").as("datasource")
cy.get(".spectrum-Button")
.contains("Save and fetch tables")
.click({ force: true })
// Intercept Request after button click & apply assertions
cy.wait("@datasource")
cy.get("@datasource")
.its("response.body")
.should("have.property", "status", 500)
cy.get(".spectrum-Button").contains("Skip table fetch").click({ force: true })
})
it("should add PostgreSQL datasource and fetch tables", () => {
// Add & configure PostgreSQL datasource
cy.selectExternalDatasource(datasource)
cy.intercept("**/datasources").as("datasource")
cy.addDatasourceConfig(datasource)
// Check response from datasource after adding configuration
cy.wait("@datasource")
cy.get("@datasource").its("response.statusCode").should("eq", 200)
cy.wait(2000)
// Confirm fetch tables was successful
cy.get(".spectrum-Table")
.eq(0)
.find(".spectrum-Table-row")
.its("length")
.should("be.gt", 0)
})
it("should define a One relationship type", () => {
// Select relationship type & configure
cy.get(".spectrum-Button")
.contains("Define relationship")
.click({ force: true })
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Picker").eq(0).click()
cy.get(".spectrum-Popover").contains("One").click()
cy.get(".spectrum-Picker").eq(1).click()
cy.get(".spectrum-Popover").contains("REGIONS").click()
cy.get(".spectrum-Picker").eq(2).click()
cy.get(".spectrum-Popover").contains("REGION_ID").click()
cy.get(".spectrum-Picker").eq(3).click()
cy.get(".spectrum-Popover").contains("COUNTRIES").click()
cy.get(".spectrum-Picker").eq(4).click()
cy.get(".spectrum-Popover").contains("REGION_ID").click()
// Save relationship & reload page
cy.get(".spectrum-Button").contains("Save").click({ force: true })
cy.reload()
})
// Confirm table length & column name
cy.get(".spectrum-Table")
.eq(1)
.find(".spectrum-Table-row")
.its("length")
.should("eq", 1)
cy.get(".spectrum-Table-cell").should("contain", "COUNTRIES to REGIONS")
})
it("should define a Many relationship type", () => {
// Select relationship type & configure
cy.get(".spectrum-Button")
.contains("Define relationship")
.click({ force: true })
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Picker").eq(0).click()
cy.get(".spectrum-Popover").contains("Many").click()
cy.get(".spectrum-Picker").eq(1).click()
cy.get(".spectrum-Popover").contains("LOCATIONS").click()
cy.get(".spectrum-Picker").eq(2).click()
cy.get(".spectrum-Popover").contains("REGIONS").click()
cy.get(".spectrum-Picker").eq(3).click()
cy.get(".spectrum-Popover").contains("COUNTRIES").click()
cy.get(".spectrum-Picker").eq(4).click()
cy.get(".spectrum-Popover").contains("COUNTRY_ID").click()
cy.get(".spectrum-Picker").eq(5).click()
cy.get(".spectrum-Popover").contains("REGION_ID").click()
// Save relationship & reload page
cy.get(".spectrum-Button").contains("Save").click({ force: true })
cy.reload()
})
// Confirm table length & relationship name
cy.get(".spectrum-Table")
.eq(1)
.find(".spectrum-Table-row")
.its("length")
.should("eq", 2)
cy.get(".spectrum-Table-cell").should(
"contain",
"LOCATIONS through COUNTRIES → REGIONS"
)
})
it("should delete a relationship", () => {
cy.get(".hierarchy-items-container").contains("PostgreSQL").click({ force: true })
cy.reload()
// Delete one relationship
cy.get(".spectrum-Table")
.eq(1)
.within(() => {
cy.get(".spectrum-Table-cell").eq(0).click({ force: true })
})
cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => {
cy.get(".spectrum-Button").contains("Delete").click({ force: true })
})
cy.reload()
cy.wait(500)
// Confirm relationship was deleted
cy.get(".spectrum-Table")
.eq(1)
.find(".spectrum-Table-row")
.its("length")
.should("eq", 1)
})
it("should add a query", () => {
// Add query
cy.get(".spectrum-Button").contains("Add query").click({ force: true })
cy.get(".spectrum-Form-item")
.eq(0)
.within(() => {
cy.get("input").type(queryName)
})
// Insert Query within Fields section
cy.get(".CodeMirror textarea")
.eq(0)
.type("SELECT * FROM books", { force: true })
// Intercept query execution
cy.intercept("**/queries/preview").as("query")
cy.get(".spectrum-Button").contains("Run Query").click({ force: true })
cy.wait(500)
cy.wait("@query")
// Assert against Status Code & Body
cy.get("@query").its("response.statusCode").should("eq", 200)
cy.get("@query").its("response.body").should("not.be.empty")
// Save query
cy.intercept("**/queries").as("saveQuery")
cy.get(".spectrum-Button").contains("Save Query").click({ force: true })
cy.wait("@saveQuery")
cy.get(".spectrum-Tabs-content", { timeout: 2000 }).should("contain", queryName)
})
it("should switch to schema with no tables", () => {
// Switch Schema - To one without any tables
cy.get(".hierarchy-items-container").contains("PostgreSQL").click({ force: true })
switchSchema("randomText")
// No tables displayed
cy.get(".spectrum-Body", { timeout: 10000 }).eq(2, { timeout: 10000 }).should("contain", "No tables found")
// Previously created query should be visible
cy.get(".spectrum-Table").should("contain", queryName)
})
it("should switch schemas", () => {
// Switch schema - To one with tables
switchSchema("1")
// Confirm tables exist - Check for specific one
cy.get(".spectrum-Table", { timeout: 20000 }).eq(0).should("contain", "test")
cy.get(".spectrum-Table")
.eq(0)
.find(".spectrum-Table-row")
.its("length")
.should("eq", 1)
// Confirm specific table visible within left nav bar
cy.get(".hierarchy-items-container").should("contain", "test")
// Switch back to public schema
switchSchema("public")
// Confirm tables exist - again
cy.get(".spectrum-Table", { timeout: 20000 }).eq(0).should("contain", "REGIONS")
cy.get(".spectrum-Table")
.eq(0)
.find(".spectrum-Table-row")
.its("length")
.should("be.gt", 1)
// Confirm specific table visible within left nav bar
cy.get(".hierarchy-items-container").should("contain", "REGIONS")
// No relationships and one query
cy.get(".spectrum-Body")
.eq(3)
.should("contain", "No relationships configured.")
cy.get(".spectrum-Table").eq(1).should("contain", queryName)
})
it("should duplicate a query", () => {
// Locate previously created query
cy.get(".nav-item")
.contains(queryName)
.siblings(".actions")
.within(() => {
cy.get(".spectrum-Icon").click({ force: true })
})
// Select and confirm duplication
cy.get(".spectrum-Menu").contains("Duplicate").click()
cy.get(".nav-item").should("contain", queryName + " (1)")
})
it("should edit a query name", () => {
// Access query
cy.get(".hierarchy-items-container", { timeout: 2000 })
//.contains(queryName + " (1)")
.contains(queryName)
.click({ force: true })
// Rename query
cy.wait(1000)
cy.get(".spectrum-Form-item", { timeout: 2000 })
.eq(0)
.within(() => {
cy.get("input").clear().type(queryRename)
})
// Click on a nav item and confirm name change
cy.get(".nav-item").first().click()
// Confirm name change
cy.get(".nav-item").should("contain", queryRename)
})
it("should delete a query", () => {
// Get query nav item - QueryName
cy.get(".nav-item")
.contains(queryRename)
.parent()
.within(() => {
cy.get(".spectrum-Icon").eq(1).click({ force: true })
})
// Select Delete
cy.get(".spectrum-Menu").contains("Delete").click()
cy.get(".spectrum-Button")
.contains("Delete Query")
.click({ force: true })
// Confirm deletion
cy.reload()
cy.get(".nav-item", { timeout: 30000 }).contains(datasource).click({ force: true })
cy.get(".nav-item", { timeout: 1000 }).should("not.contain", queryRename)
})
const switchSchema = schema => {
// Edit configuration - Change Schema
cy.get(".spectrum-Textfield")
.eq(6)
.within(() => {
cy.get("input").clear().type(schema)
})
// Save configuration & fetch
cy.get(".spectrum-Button").contains("Save").click({ force: true })
cy.get(".spectrum-Button")
.contains("Fetch tables")
.click({ force: true })
// Click fetch tables again within modal
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button")
.contains("Fetch tables")
.click({ force: true })
})
cy.reload()
cy.wait(1000)
}
}
})
})

View file

@ -1,45 +0,0 @@
import filterTests from "../../support/filterTests"
filterTests(["smoke", "all"], () => {
xcontext("REST Datasource Testing", () => {
before(() => {
cy.login()
cy.createTestApp()
})
const datasource = "REST"
const restUrl = "https://api.openbrewerydb.org/breweries"
it("Should add REST datasource with incorrect API", () => {
// Select REST datasource
cy.selectExternalDatasource(datasource)
// Enter incorrect api & attempt to send query
cy.get(".query-buttons", { timeout: 1000 }).contains("Add query").click({ force: true })
cy.intercept("**/preview").as("queryError")
cy.get("input").clear().type("random text")
cy.get(".spectrum-Button").contains("Send").click({ force: true })
// Intercept Request after button click & apply assertions
cy.wait("@queryError")
cy.get("@queryError")
.its("response.body")
.should("have.property", "message", "Invalid URL: http://random text")
cy.get("@queryError")
.its("response.body")
.should("have.property", "status", 400)
})
it("should add and configure a REST datasource", () => {
// Select REST datasource and create query
cy.selectExternalDatasource(datasource)
cy.wait(500)
// createRestQuery confirms query creation
cy.createRestQuery("GET", restUrl, "/breweries")
// Confirm status code response within REST datasource
cy.get(".stats", { timeout: 1000 }).within(() => {
cy.get(".spectrum-FieldLabel")
.eq(0)
.should("contain", 200)
})
})
})
})

View file

@ -1,147 +0,0 @@
import filterTests from "../support/filterTests"
const interact = require("../support/interact")
filterTests(["smoke", "all"], () => {
context("Query Level Transformers", () => {
before(() => {
cy.login()
cy.createTestApp()
})
it("should write a transformer function", () => {
// Add REST datasource - contains API for breweries
const datasource = "REST"
const restUrl = "https://api.openbrewerydb.org/breweries"
cy.selectExternalDatasource(datasource)
cy.createRestQuery("GET", restUrl, "breweries")
cy.reload()
cy.contains(".nav-item-content", "breweries", { timeout: 20000 }).click()
cy.contains(interact.SPECTRUM_TABS_ITEM, "Transformer", { timeout: 5000 }).click({ force: true })
// Get Transformer Function from file
cy.readFile("cypress/support/queryLevelTransformerFunction.js").then(
transformerFunction => {
cy.get(interact.CODEMIRROR_TEXTAREA, { timeout: 5000 })
// Highlight current text and overwrite with file contents
.type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", {
force: true,
})
.type(transformerFunction, { parseSpecialCharSequences: false })
}
)
// Send Query
cy.intercept("**/queries/preview").as("query")
cy.get(interact.SPECTRUM_BUTTON).contains("Save").click({ force: true })
cy.get(interact.SPECTRUM_BUTTON).contains("Send").click({ force: true })
cy.wait("@query")
// Assert against Status Code, body, & body rows
cy.get("@query").its("response.statusCode").should("eq", 200)
cy.get("@query").its("response.body").should("not.be.empty")
cy.get("@query").its("response.body.rows").should("not.be.empty")
})
it("should add data to the previous query", () => {
// Add REST datasource - contains API for breweries
const datasource = "REST"
const restUrl = "https://api.openbrewerydb.org/breweries"
cy.selectExternalDatasource(datasource)
cy.createRestQuery("GET", restUrl, "breweries")
cy.reload()
cy.contains(".nav-item-content", "breweries", { timeout: 2000 }).click()
cy.contains(interact.SPECTRUM_TABS_ITEM, "Transformer", { timeout: 5000 }).click({ force: true })
// Get Transformer Function with Data from file
cy.readFile(
"cypress/support/queryLevelTransformerFunctionWithData.js"
).then(transformerFunction => {
//console.log(transformerFunction[1])
cy.get(interact.CODEMIRROR_TEXTAREA)
// Highlight current text and overwrite with file contents
.type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", {
force: true,
})
.type(transformerFunction, { parseSpecialCharSequences: false })
})
// Send Query
cy.intercept("**/queries/preview").as("query")
cy.get(interact.SPECTRUM_BUTTON).contains("Send").click({ force: true })
cy.wait("@query")
// Assert against Status Code, body, & body rows
cy.get("@query").its("response.statusCode").should("eq", 200)
cy.get("@query").its("response.body").should("not.be.empty")
cy.get("@query").its("response.body.rows").should("not.be.empty")
})
it("should run an invalid query within the transformer section", () => {
// Add REST datasource - contains API for breweries
const datasource = "REST"
const restUrl = "https://api.openbrewerydb.org/breweries"
cy.selectExternalDatasource(datasource)
cy.createRestQuery("GET", restUrl, "breweries")
cy.reload()
cy.contains(".nav-item-content", "breweries", { timeout: 10000 }).click()
cy.contains(interact.SPECTRUM_TABS_ITEM, "Transformer", { timeout: 5000 }).click({ force: true })
// Clear the code box and add "test"
cy.get(interact.CODEMIRROR_TEXTAREA)
.type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", {
force: true,
})
.type("test")
// Run Query and intercept
cy.intercept("**/preview").as("queryError")
cy.get(interact.SPECTRUM_BUTTON).contains("Send").click({ force: true })
cy.wait("@queryError")
cy.wait(500)
// Assert against message and status for the query error
cy.get("@queryError")
.its("response.body")
.should("have.property", "message", "test is not defined")
cy.get("@queryError")
.its("response.body")
.should("have.property", "status", 400)
})
xit("should run an invalid query via POST request", () => {
// POST request with transformer as null
cy.request({
method: "POST",
url: `${Cypress.config().baseUrl}/api/queries/`,
body: {
fields: { headers: {}, queryString: null, path: null },
parameters: [],
schema: {},
name: "test",
queryVerb: "read",
transformer: null,
datasourceId: "test",
},
// Expected 400 error - Transformer must be a string
failOnStatusCode: false,
}).then(response => {
expect(response.status).to.equal(400)
expect(response.body.message).to.include(
'Invalid body - "transformer" must be a string'
)
})
})
xit("should run an empty query", () => {
// POST request with Transformer as an empty string
cy.request({
method: "POST",
url: `${Cypress.config().baseUrl}/api/queries/preview`,
body: {
fields: { headers: {}, queryString: null, path: null },
queryVerb: "read",
transformer: "",
datasourceId: "test",
},
// Expected 400 error - Transformer is not allowed to be empty
failOnStatusCode: false,
}).then(response => {
expect(response.status).to.equal(400)
expect(response.body.message).to.include(
'Invalid body - "transformer" is not allowed to be empty'
)
})
})
})
})

View file

@ -1,112 +0,0 @@
import filterTests from "../support/filterTests"
const interact = require("../support/interact")
filterTests(["all"], () => {
xcontext("Rename an App", () => {
beforeEach(() => {
cy.login()
cy.createTestApp()
})
it("should rename an unpublished application", () => {
const appName = "Cypress Tests"
const appRename = "Cypress Renamed"
// Rename app, Search for app, Confirm name was changed
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
renameApp(appName, appRename)
cy.reload()
cy.searchForApplication(appRename)
cy.get(interact.APP_TABLE).find(interact.TITLE).should("have.length", 1)
cy.applicationInAppTable(appRename)
// Set app name back to Cypress Tests
cy.reload()
renameApp(appRename, appName)
})
xit("Should rename a published application", () => {
// It is not possible to rename a published application
const appName = "Cypress Tests"
const appRename = "Cypress Renamed"
// Publish the app
cy.get(interact.TOP_RIGHT_NAV)
cy.get(interact.SPECTRUM_BUTTON)
.contains("Publish")
.click({ force: true })
cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => {
// Click publish again within the modal
cy.get(interact.SPECTRUM_BUTTON)
.contains("Publish")
.click({ force: true })
})
// Rename app, Search for app, Confirm name was changed
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
renameApp(appName, appRename, true)
cy.get(interact.APP_TABLE).find(interact.WRAPPER).should("have.length", 1)
cy.applicationInAppTable(appRename)
})
it("Should try to rename an application to have no name", () => {
const appName = "Cypress Tests"
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
renameApp(appName, " ", false, true)
// Close modal and confirm name has not been changed
cy.get(interact.SPECTRUM_DIALOG_GRID, { timeout: 1000 }).contains("Cancel").click()
cy.applicationInAppTable(appName)
})
xit("Should create two applications with the same name", () => {
// It is not possible to have applications with the same name
const appName = "Cypress Tests"
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.get(interact.SPECTRUM_BUTTON), { timeout: 500 }
.contains("Create app")
.click({ force: true })
cy.contains(/Start from scratch/).click()
cy.get(interact.SPECTRUM_MODAL).within(() => {
cy.get("input").eq(0).type(appName)
cy.get(interact.SPECTRUM_BUTTON_GROUP)
.contains("Create app")
.click({ force: true })
cy.get(interact.ERROR).should(
"have.text",
"Another app with the same name already exists"
)
})
})
it("should validate application names", () => {
// App name must be letters, numbers and spaces only
// This test checks numbers and special characters specifically
const appName = "Cypress Tests"
const numberName = 12345
const specialCharName = "£$%^"
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
renameApp(appName, numberName)
cy.applicationInAppTable(numberName)
renameApp(numberName, specialCharName)
cy.get(interact.ERROR).should(
"have.text",
"App name must be letters, numbers and spaces only"
)
// Set app name back to Cypress Tests
renameApp(numberName, appName)
})
const renameApp = (originalName, changedName, published, noName) => {
cy.searchForApplication(originalName)
cy.get(interact.APP_TABLE, { timeout: 1000 }).within(() => {
cy.get(".app-row-actions button")
.contains("Manage")
.eq(0)
.click({ force: true })
})
cy.get(".spectrum-Tabs-item").contains("Settings").click()
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
cy.get(".settings-tab").should("be.visible")
cy.get(".details-section .page-action button")
.contains("Edit")
.click({ force: true })
cy.updateAppName(changedName, noName)
}
})
})

View file

@ -1,75 +0,0 @@
import filterTests from "../support/filterTests"
const interact = require('../support/interact')
filterTests(['smoke', 'all'], () => {
xcontext("Revert apps", () => {
before(() => {
cy.login()
cy.createTestApp()
})
it("should try to revert an unpublished app", () => {
// Click revert icon
cy.get(interact.TOP_RIGHT_NAV).within(() => {
cy.get(interact.AREA_LABEL_REVERT).click({ force: true })
})
cy.get(interact.SPECTRUM_MODAL).within(() => {
// Enter app name before revert
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).type("Cypress Tests")
cy.intercept('**/revert').as('revertApp')
// Click Revert
cy.get(interact.SPECTRUM_BUTTON).contains("Revert").click({ force: true })
// Intercept Request after button click & apply assertions
cy.wait("@revertApp")
cy.get("@revertApp").its('response.body').should('have.property', 'message', "App has not yet been deployed")
cy.get("@revertApp").its('response.body').should('have.property', 'status', 400)
})
})
it("should revert a published app", () => {
cy.navigateToFrontend()
// Add initial component - Paragraph
cy.searchAndAddComponent("Paragraph")
// Publish app
cy.get(interact.SPECTRUM_BUTTON).contains("Publish").click({ force: true })
cy.get(interact.SPECTRUM_BUTTON_GROUP).within(() => {
cy.get(interact.SPECTRUM_BUTTON).contains("Publish").click({ force: true })
})
cy.wait(1000) // Wait for next modal to finish loading
cy.get(interact.SPECTRUM_BUTTON_GROUP, { timeout: 1000 }).within(() => {
cy.get(interact.SPECTRUM_BUTTON).contains("Done").click({ force: true })
})
// Add second component - Button
cy.searchAndAddComponent("Button")
// Click Revert
cy.get(interact.TOP_RIGHT_NAV).within(() => {
cy.get(interact.AREA_LABEL_REVERT).click({ force: true })
})
cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => {
cy.get("input").type("Cypress Tests")
// Click Revert
cy.get(interact.SPECTRUM_BUTTON).contains("Revert").click({ force: true })
cy.wait(2000) // Wait for app to finish reverting
})
// Confirm Paragraph component is still visible
cy.get(interact.ROOT, { timeout: 1000 }).contains("New Paragraph")
// Confirm Button component is not visible
cy.get(interact.ROOT, { timeout: 1000 }).should("not.have.text", "New Button")
})
it("should enter incorrect app name when reverting", () => {
// Click Revert
cy.get(interact.TOP_RIGHT_NAV, { timeout: 1000 }).within(() => {
cy.get(interact.AREA_LABEL_REVERT).click({ force: true })
})
// Enter incorrect app name
cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => {
cy.get("input").type("Cypress Tests")
// Revert button within modal should be disabled
cy.get(interact.SPECTRUM_BUTTON).eq(1).should('be.disabled')
})
})
})
})

View file

@ -1,23 +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
require("cypress-terminal-report/src/installLogsPrinter")(on)
}

View file

@ -1,945 +0,0 @@
Cypress.on("uncaught:exception", () => {
return false
})
// ACCOUNTS & USERS
Cypress.Commands.add("login", (email, password) => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 })
cy.url()
.should("include", "/builder/")
.then(url => {
if (url.includes("builder/admin")) {
// create admin user
cy.get("input").first().type("test@test.com")
cy.get('input[type="password"]').first().type("test")
cy.get('input[type="password"]').eq(1).type("test")
cy.contains("Create super admin user").click({ force: true })
}
if (url.includes("builder/auth") || url.includes("builder/admin")) {
// login
cy.contains("Sign in to Budibase").then(() => {
if (email == null) {
cy.get("input").first().type("test@test.com")
cy.get('input[type="password"]').type("test")
} else {
cy.get("input").first().type(email)
cy.get('input[type="password"]').type(password)
}
cy.get("button").first().click({ force: true })
cy.wait(1000)
})
}
})
})
Cypress.Commands.add("logOut", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 })
cy.get(".user-dropdown .avatar > .icon").click({ force: true })
cy.get(".spectrum-Popover[data-cy='user-menu']").within(() => {
cy.get("li[data-cy='user-logout']").click({ force: true })
})
cy.wait(2000)
})
Cypress.Commands.add("logoutNoAppGrid", () => {
// Logs user out when app grid is not present
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 })
cy.get(".avatar > .icon").click({ force: true })
cy.get(".spectrum-Popover[data-cy='user-menu']").within(() => {
cy.get(".spectrum-Menu-item").contains("Log out").click({ force: true })
})
cy.wait(2000)
})
Cypress.Commands.add("createUser", (email, permission) => {
cy.contains("Users").click()
cy.get(`[data-cy="add-user"]`).click()
cy.get(".spectrum-Dialog-grid").within(() => {
// Enter email
cy.get(".spectrum-Textfield-input").clear().click().type(email)
// Select permission, if applicable
// Default is App User
if (permission != null) {
cy.get(".spectrum-Picker-label").click()
cy.get(".spectrum-Menu").within(() => {
cy.get(".spectrum-Menu-item")
.contains(permission)
.click({ force: true })
})
}
// Add user
cy.get(".spectrum-Button").contains("Add users").click({ force: true })
cy.get(".spectrum-ActionButton").contains("Add email").should("not.exist")
})
// Onboarding modal
cy.get(".spectrum-Dialog-grid", { timeout: 5000 }).contains(
"Choose your onboarding"
)
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".onboarding-type").eq(1).click()
cy.get(".spectrum-Button").contains("Done").click({ force: true })
cy.get(".spectrum-Button").contains("Cancel").should("not.exist")
})
// Accounts created modal - Click Done button
cy.get(".spectrum-Button").contains("Done").click({ force: true })
})
Cypress.Commands.add("deleteUser", email => {
// Assumes user has access to Users section
cy.contains("Users", { timeout: 2000 }).click()
cy.contains(email).click()
cy.get(".title").within(() => {
cy.get(".spectrum-Icon").click({ force: true })
})
cy.get(".spectrum-Menu").within(() => {
cy.get(".spectrum-Menu-item").contains("Delete").click({ force: true })
})
cy.get(".spectrum-Dialog-grid").contains("Delete user").click({ force: true })
})
Cypress.Commands.add("updateUserInformation", (firstName, lastName) => {
cy.get(".user-dropdown .icon", { timeout: 2000 }).click({
force: true,
})
cy.get(".spectrum-Popover[data-cy='user-menu']").within(() => {
cy.get("li[data-cy='user-info']").click({ force: true })
})
cy.get(".spectrum-Modal.is-open").within(() => {
cy.get("[data-cy='user-first-name']").clear()
if (!firstName || firstName == "") {
cy.get("[data-cy='user-first-name']").invoke("val").should("be.empty")
} else {
cy.get("[data-cy='user-first-name']")
.type(firstName)
.should("have.value", firstName)
.blur()
}
cy.get("[data-cy='user-last-name']").clear()
if (!lastName || lastName == "") {
cy.get("[data-cy='user-last-name']").invoke("val").should("be.empty")
} else {
cy.get("[data-cy='user-last-name']")
.type(lastName)
.should("have.value", lastName)
.blur()
}
cy.get(".confirm-wrap").within(() => {
cy.get("button").contains("Save").click({ force: true })
})
cy.get(".spectrum-Dialog-grid").should("not.exist")
})
})
Cypress.Commands.add("setUserRole", (user, role) => {
cy.contains("Users").click()
cy.contains(user).click()
// Set Role
cy.wait(500)
cy.get(".spectrum-Form-itemField")
.eq(3)
.within(() => {
cy.get(".spectrum-Picker-label").click({ force: true })
})
cy.get(".spectrum-Menu").within(() => {
cy.get(".spectrum-Menu-itemLabel").contains(role).click({ force: true })
})
cy.get(".spectrum-Form-itemField").eq(3).should("contain", role)
})
// APPLICATIONS
Cypress.Commands.add("createTestApp", () => {
const appName = "Cypress Tests"
cy.deleteApp(appName)
cy.createApp(appName, "This app is used for Cypress testing.")
})
Cypress.Commands.add("createApp", (name, addDefaultTable) => {
const shouldCreateDefaultTable =
typeof addDefaultTable != "boolean" ? true : addDefaultTable
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 })
cy.url({ timeout: 30000 }).should("include", "/apps")
cy.get(`[data-cy="create-app-btn"]`, { timeout: 5000 }).click({ force: true })
// If apps already exist
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`, {
timeout: 5000,
})
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(`[data-cy="create-app-btn"]`, { timeout: 5000 }).click({
force: true,
})
}
})
cy.get(".spectrum-Modal").within(() => {
cy.get("input").eq(0).should("have.focus")
if (name && name != "") {
cy.get("input").eq(0).clear()
cy.get("input").eq(0).type(name).should("have.value", name).blur()
}
cy.get(".spectrum-ButtonGroup")
.contains("Create app")
.click({ force: true })
cy.wait(2000)
})
if (shouldCreateDefaultTable) {
cy.createTable("Cypress Tests", true)
}
})
Cypress.Commands.add("deleteApp", name => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 })
cy.wait(2000)
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
const findAppName = val.some(val => val.name == name)
if (findAppName) {
if (val.length > 0) {
const appId = val.reduce((acc, app) => {
if (name === app.name) {
acc = app.appId
}
return acc
}, "")
if (appId == "") {
return
}
// Go to app overview
const appIdParsed = appId.split("_").pop()
const actionEleId = `[data-cy=row_actions_${appIdParsed}]`
cy.get(actionEleId).click()
cy.get(`[aria-label="ShowMenu"]`).click()
cy.get(".spectrum-Menu").within(() => {
cy.contains("Overview").click()
})
cy.wait(500)
// Unpublish first if needed
cy.get(`[data-cy="app-status"]`).then($status => {
if ($status.text().includes("- Unpublish")) {
// Exact match for Unpublish
cy.contains("Unpublish").click({ force: true })
cy.get(".spectrum-Modal").within(() => {
cy.contains("Unpublish app").click({ force: true })
})
}
})
// Delete app
cy.get(".app-overview-actions-icon").within(() => {
cy.get(".spectrum-Icon").click({ force: true })
})
cy.get(".spectrum-Menu").contains("Delete").click({ force: true })
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get("input").type(name)
})
cy.get(".spectrum-Button--warning").click()
} else {
return
}
} else {
return
}
})
})
Cypress.Commands.add("deleteAllApps", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 })
cy.wait(500)
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`, {
timeout: 5000,
})
.its("body")
.then(val => {
for (let i = 0; i < val.length; i++) {
cy.deleteApp(val[i].name)
cy.reload()
}
})
})
Cypress.Commands.add("unlockApp", unlock_config => {
let config = { ...unlock_config }
cy.get(".spectrum-Modal .spectrum-Dialog[data-cy='app-lock-modal']")
.should("be.visible")
.within(() => {
if (config.owned) {
cy.get(".spectrum-Dialog-heading").contains("Locked by you")
cy.get(".lock-expiry-body").contains(
"This lock will expire in 10 minutes from now"
)
cy.intercept("**/lock").as("unlockApp")
cy.get(".spectrum-Button")
.contains("Release Lock")
.click({ force: true })
cy.wait("@unlockApp")
cy.get("@unlockApp").its("response.statusCode").should("eq", 200)
cy.get("@unlockApp").its("response.body").should("deep.equal", {
message: "Lock released successfully.",
})
} else {
//Show the name ?
cy.get(".lock-expiry-body").should("not.be.visible")
cy.get(".spectrum-Button").contains("Done")
}
})
})
Cypress.Commands.add("updateAppName", (changedName, noName) => {
cy.get(".spectrum-Modal").within(() => {
if (noName == true) {
cy.get("input").clear()
cy.get(".spectrum-Dialog-grid")
.click()
.contains("App name must be letters, numbers and spaces only")
return cy
}
cy.get("input").clear()
cy.get("input")
.eq(0)
.type(changedName)
.should("have.value", changedName)
.blur()
cy.get(".spectrum-ButtonGroup").contains("Save").click({ force: true })
cy.wait(500)
})
})
Cypress.Commands.add("publishApp", resolvedAppPath => {
// Assumes you have navigated to an application first
cy.get(".toprightnav button.spectrum-Button")
.contains("Publish")
.click({ force: true })
cy.get(".spectrum-Modal [data-cy='deploy-app-modal']")
.should("be.visible")
.within(() => {
cy.get(".spectrum-Button").contains("Publish").click({ force: true })
cy.wait(1000)
})
// Verify that the app url is presented correctly to the user
cy.get(".spectrum-Modal [data-cy='deploy-app-success-modal']")
.should("be.visible")
.within(() => {
let appUrl = Cypress.config().baseUrl + "/app/" + resolvedAppPath
cy.get("[data-cy='deployed-app-url'] input").should("have.value", appUrl)
cy.get(".spectrum-Button").contains("Done").click({ force: true })
})
})
Cypress.Commands.add("alterAppVersion", (appId, version) => {
return cy
.request("put", `${Cypress.config().baseUrl}/api/applications/${appId}`, {
version: version || "0.0.1-alpha.0",
})
.then(resp => {
expect(resp.status).to.eq(200)
})
})
Cypress.Commands.add("importApp", (exportFilePath, name) => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 })
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(`[data-cy="create-app-btn"]`).click({ force: true })
}
cy.wait(500)
cy.get(`[data-cy="import-app-btn"]`).click({
force: true,
})
})
cy.get(".spectrum-Modal").within(() => {
cy.get("input").eq(1).should("have.focus")
cy.get(".spectrum-Dropzone").selectFile(exportFilePath, {
action: "drag-drop",
})
cy.get(".gallery .filename").contains("exported-app.txt")
if (name && name != "") {
cy.get("input").eq(0).type(name).should("have.value", name).blur()
}
cy.get(".confirm-wrap button")
.should("not.be.disabled")
.click({ force: true })
cy.wait(3000)
})
})
// Filters visible with 1 or more
Cypress.Commands.add("searchForApplication", appName => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 })
cy.wait(2000)
// No app filter functionality if only 1 app exists
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length < 2) {
return
} else {
// Searches for the app
cy.get(".spectrum-Search").then(() => {
cy.get(".spectrum-Textfield").within(() => {
cy.get("input").eq(0).clear({ force: true })
cy.get("input").eq(0).type(appName, { force: true })
})
})
}
})
})
// Assumes there are no others
Cypress.Commands.add("applicationInAppTable", appName => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 })
cy.get(".app-table", { timeout: 30000 }).within(() => {
cy.get(".title").contains(appName).should("exist")
})
})
Cypress.Commands.add("createAppFromScratch", appName => {
cy.get(`[data-cy="create-app-btn"]`)
.contains("Start from scratch")
.click({ force: true })
cy.get(".spectrum-Modal").within(() => {
cy.get("input")
.eq(0)
.clear()
.type(appName)
.should("have.value", appName)
.blur()
cy.get(".spectrum-ButtonGroup").contains("Create app").click()
cy.wait(10000)
})
cy.createTable("Cypress Tests", true)
})
// TABLES
Cypress.Commands.add("createTable", (tableName, initialTable) => {
// Creates an internal Budibase DB table
if (!initialTable) {
cy.navigateToDataSection()
}
cy.get(`[data-cy="new-datasource"]`, { timeout: 20000 }).click()
cy.wait(2000)
cy.get(".item", { timeout: 2000 })
.contains("Budibase DB")
.click({ force: true })
.then(() => {
cy.get(".spectrum-Button", { timeout: 2000 })
.contains("Continue")
.click({ force: true })
})
cy.get(".spectrum-Modal").contains("Create Table", { timeout: 10000 })
cy.get(".spectrum-Modal", { timeout: 2000 }).within(() => {
cy.get("input", { timeout: 2000 }).first().type(tableName).blur()
cy.get(".spectrum-ButtonGroup").contains("Create").click()
})
// Ensure modal has closed and table is created
cy.get(".spectrum-Modal", { timeout: 2000 }).should("not.exist")
cy.get(".nav-item", { timeout: 2000 })
.contains("Budibase DB")
.click({ force: true })
cy.get(".nav-item-content", { timeout: 2000 }).should("contain", tableName)
})
Cypress.Commands.add("createTestTableWithData", () => {
cy.createTable("dog")
cy.addColumn("dog", "name", "Text")
cy.addColumn("dog", "age", "Number")
})
Cypress.Commands.add(
"addColumn",
(tableName, columnName, type, multiOptions = null) => {
// Select Table
cy.selectTable(tableName)
cy.contains(".nav-item", tableName).click()
cy.contains("Create column").click()
// Configure column
cy.get(".spectrum-Modal").within(() => {
cy.get("input").first().type(columnName)
// Unset table display column
cy.contains("display column").click({ force: true })
cy.get(".spectrum-Picker-label").click()
cy.contains(type).click()
// Add options for Multi-select Type
if (multiOptions !== null) {
cy.get(".spectrum-Textfield-input").eq(1).type(multiOptions)
}
cy.contains("Save Column").click()
})
}
)
Cypress.Commands.add("addRow", values => {
cy.contains("Create row").click()
cy.get(".spectrum-Modal").within(() => {
for (let i = 0; i < values.length; i++) {
cy.get("input").eq(i).type(values[i]).blur()
}
cy.get(".spectrum-ButtonGroup").contains("Create").click()
})
})
Cypress.Commands.add("addRowMultiValue", values => {
cy.contains("Create row").click()
cy.get(".spectrum-Modal").within(() => {
cy.get(".spectrum-Form-itemField")
.click()
.then(() => {
cy.get(".spectrum-Popover").within(() => {
for (let i = 0; i < values.length; i++) {
cy.get(".spectrum-Menu-item").eq(i).click()
}
})
cy.get(".spectrum-Dialog-grid").click("top")
cy.get(".spectrum-ButtonGroup").contains("Create").click()
})
})
})
Cypress.Commands.add("selectTable", tableName => {
cy.get(".nav-item").contains("Budibase DB").click()
cy.contains(".nav-item", tableName).click()
})
Cypress.Commands.add("addCustomSourceOptions", totalOptions => {
cy.get('[data-cy="customOptions-prop-control"]').within(() => {
cy.get(".spectrum-ActionButton-label").click({ force: true })
})
for (let i = 0; i < totalOptions; i++) {
// Add radio button options
cy.get(".spectrum-Button-label", { timeout: 1000 })
.contains("Add Option")
.click({ force: true })
.then(() => {
cy.get("[placeholder='Label']", { timeout: 500 }).eq(i).type(i)
cy.get("[placeholder='Value']").eq(i).type(i)
})
}
// Save options
cy.get(".spectrum-Button").contains("Save").click({ force: true })
})
// DESIGN SECTION
Cypress.Commands.add("searchAndAddComponent", component => {
// Open component menu
cy.get(".icon-side-nav").within(() => {
cy.get(".icon-side-nav-item").eq(1).click()
})
cy.get(".add-component > .spectrum-Button")
.contains("Add component")
.click({ force: true })
cy.get(".container", { timeout: 1000 }).within(() => {
cy.get(".title").should("contain", "Add component")
// Search and add component
cy.get(".spectrum-Textfield-input").clear().type(component)
cy.get(".body").within(() => {
cy.get(".component")
.contains(new RegExp("^" + component + "$"), { timeout: 3000 })
.click({ force: true })
})
})
cy.wait(1000)
cy.location().then(loc => {
const params = loc.pathname.split("/")
const componentId = params[params.length - 1]
cy.getComponent(componentId, { timeout: 3000 }).should("exist")
return cy.wrap(componentId)
})
})
Cypress.Commands.add("deleteComponentByName", componentName => {
cy.get(".body")
.eq(0)
.contains(componentName)
.siblings(".actions")
.within(() => {
cy.get(".spectrum-Icon").click({ force: true })
})
cy.get(".spectrum-Menu").contains("Delete").click()
cy.get(".spectrum-Dialog").contains("Delete Component").click()
})
Cypress.Commands.add("addComponent", (category, component) => {
if (category) {
cy.get(`[data-cy="category-${category}"]`, { timeout: 3000 }).click({
force: true,
})
}
cy.wait(500)
if (component) {
cy.get(`[data-cy="component-${component}"]`, { timeout: 3000 }).click({
force: true,
})
}
cy.wait(1000)
cy.location().then(loc => {
const params = loc.pathname.split("/")
const componentId = params[params.length - 1]
cy.getComponent(componentId, { timeout: 3000 }).should("exist")
return cy.wrap(componentId)
})
})
Cypress.Commands.add("getComponent", componentId => {
return cy
.get("iframe")
.its("0.contentDocument")
.should("exist")
.its("body")
.should("not.be.undefined")
.then(cy.wrap)
.find(`[data-id='${componentId}']`)
})
Cypress.Commands.add("createScreen", (route, accessLevelLabel) => {
// Blank Screen
cy.contains("Design").click()
cy.get(".spectrum-Button").contains("Add screen").click({ force: true })
cy.get(".spectrum-Modal").within(() => {
cy.get("[data-cy='blank-screen']").click()
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
})
cy.wait(500)
cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => {
cy.get(".spectrum-Form-itemField").eq(0).type(route)
cy.get(".confirm-wrap").contains("Continue").click({ force: true })
})
cy.get(".spectrum-Modal", { timeout: 1000 }).within(() => {
if (accessLevelLabel) {
cy.get(".spectrum-Picker-label").click()
cy.wait(500)
cy.contains(accessLevelLabel).click()
}
cy.get(".spectrum-Button").contains("Done").click({ force: true })
})
})
Cypress.Commands.add(
"createDatasourceScreen",
(datasourceNames, accessLevelLabel) => {
cy.contains("Design").click()
cy.get(".spectrum-Button").contains("Add screen").click({ force: true })
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get("[data-cy='autogenerated-screens']").click()
cy.intercept("**/api/datasources").as("autoScreens")
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
cy.wait("@autoScreens")
cy.wait(5000)
})
cy.get("[data-cy='autogenerated-screens']").should("not.exist")
cy.get("[data-cy='data-source-modal']", { timeout: 10000 }).within(() => {
for (let i = 0; i < datasourceNames.length; i++) {
cy.get(".data-source-entry")
.contains(datasourceNames[i], { timeout: 20000 })
.click({ force: true })
// Ensure the check mark is visible
cy.get(".data-source-entry")
.contains(datasourceNames[i])
.get(".data-source-check", { timeout: 20000 })
.should("exist")
}
cy.get(".spectrum-Button").contains("Confirm").click({ force: true })
})
cy.get(".spectrum-Modal", { timeout: 10000 }).within(() => {
if (accessLevelLabel) {
cy.get(".spectrum-Picker-label", { timeout: 10000 }).click()
cy.contains(accessLevelLabel).click()
}
cy.get(".spectrum-Button").contains("Done").click({ force: true })
})
cy.contains("Design").click()
}
)
Cypress.Commands.add(
"createAutogeneratedScreens",
(screenNames, accessLevelLabel) => {
cy.navigateToAutogeneratedModal()
for (let i = 0; i < screenNames.length; i++) {
cy.get(".data-source-entry").contains(screenNames[i]).click()
}
cy.get(".spectrum-Modal").within(() => {
if (accessLevelLabel) {
cy.get(".spectrum-Picker-label").click()
cy.wait(500)
cy.contains(accessLevelLabel).click()
}
cy.get(".spectrum-Button").contains("Confirm").click({ force: true })
cy.wait(4000)
})
}
)
Cypress.Commands.add("filterScreensAccessLevel", accessLevel => {
// Filters screens by access level dropdown
cy.get(".body").within(() => {
cy.get(".spectrum-Form-item").eq(1).click()
})
cy.get(".spectrum-Menu").within(() => {
cy.contains(accessLevel).click()
})
})
Cypress.Commands.add("deleteScreen", screen => {
// Navigates to Design section and deletes specified screen
cy.contains("Design").click()
cy.get(".body").within(() => {
cy.contains(screen)
.siblings(".actions")
.within(() => {
cy.get(".spectrum-Icon").click({ force: true })
})
})
cy.get(".spectrum-Menu > .spectrum-Menu-item > .spectrum-Menu-itemLabel")
.contains("Delete")
.click()
cy.get(
".spectrum-Dialog-grid > .spectrum-ButtonGroup > .confirm-wrap > .spectrum-Button"
).click({ force: true })
cy.get(".spectrum-Dialog-grid", { timeout: 10000 }).should("not.exist")
})
Cypress.Commands.add("deleteAllScreens", () => {
// Deletes all screens
cy.get(".body")
.find(".nav-item")
.its("length")
.then(len => {
for (let i = 0; i < len; i++) {
cy.get(".body > .nav-item")
.eq(0)
.invoke("text")
.then(text => {
cy.deleteScreen(text.trim())
})
}
})
})
// NAVIGATION
Cypress.Commands.add("navigateToFrontend", () => {
// Clicks on Design tab and then the Home nav item
cy.wait(500)
cy.intercept("**/preview").as("preview")
cy.contains("Design").click()
cy.wait("@preview")
cy.get("@preview").then(res => {
if (res.statusCode != 200) {
cy.reload()
}
})
cy.get(".spectrum-Search", { timeout: 20000 }).type("/")
cy.get(".nav-item", { timeout: 2000 }).contains("home").click({ force: true })
})
Cypress.Commands.add("navigateToDataSection", () => {
// Clicks on the Data tab
cy.wait(500)
cy.contains("Data").click()
})
Cypress.Commands.add("navigateToAutogeneratedModal", () => {
// Screen name must already exist within datasource
cy.contains("Design").click()
cy.get(".spectrum-Button").contains("Add screen").click({ force: true })
cy.get(".spectrum-Modal").within(() => {
cy.get(".item", { timeout: 2000 })
.contains("Autogenerated screens")
.click({ force: true })
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
cy.wait(500)
})
})
// DATASOURCES
Cypress.Commands.add("selectExternalDatasource", datasourceName => {
// Navigates to Data Section
cy.navigateToDataSection()
// Open Datasource modal
cy.get(".container").within(() => {
cy.get("[data-cy='new-datasource']").click()
})
// Clicks specified datasource & continue
cy.get(".item-list", { timeout: 1000 }).contains(datasourceName).click()
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
})
cy.wait(500)
})
Cypress.Commands.add("addDatasourceConfig", (datasource, skipFetch) => {
// selectExternalDatasource should be called prior to this
// Adds the config for specified datasource & fetches tables
// Currently supports MySQL, PostgreSQL, Oracle
// Host IP Address
cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => {
cy.get(".form-row")
.eq(0)
.within(() => {
cy.get(".spectrum-Textfield").within(() => {
if (datasource == "Oracle") {
cy.get("input").clear().type(Cypress.env("oracle").HOST)
} else {
cy.get("input")
.clear({ force: true })
.type(Cypress.env("HOST_IP"), { force: true })
}
})
})
})
// Database Name
cy.get(".spectrum-Dialog-grid").within(() => {
if (datasource == "MySQL") {
cy.get(".form-row")
.eq(4)
.within(() => {
cy.get("input").clear().type(Cypress.env("mysql").DATABASE)
})
} else {
cy.get(".form-row")
.eq(2)
.within(() => {
if (datasource == "PostgreSQL") {
cy.get("input").clear().type(Cypress.env("postgresql").DATABASE)
}
if (datasource == "Oracle") {
cy.get("input").clear().type(Cypress.env("oracle").DATABASE)
}
})
}
})
// User
cy.get(".spectrum-Dialog-grid").within(() => {
if (datasource == "MySQL") {
cy.get(".form-row")
.eq(2)
.within(() => {
cy.get("input").clear().type(Cypress.env("mysql").USER)
})
} else {
cy.get(".form-row")
.eq(3)
.within(() => {
if (datasource == "PostgreSQL") {
cy.get("input").clear().type(Cypress.env("postgresql").USER)
}
if (datasource == "Oracle") {
cy.get("input").clear().type(Cypress.env("oracle").USER)
}
})
}
})
// Password
cy.get(".spectrum-Dialog-grid").within(() => {
if (datasource == "MySQL") {
cy.get(".form-row")
.eq(3)
.within(() => {
cy.get("input").clear().type(Cypress.env("mysql").PASSWORD)
})
} else {
cy.get(".form-row")
.eq(4)
.within(() => {
if (datasource == "PostgreSQL") {
cy.get("input").clear().type(Cypress.env("postgresql").PASSWORD)
}
if (datasource == "Oracle") {
cy.get("input").clear().type(Cypress.env("oracle").PASSWORD)
}
})
}
})
// Click to fetch tables
if (skipFetch) {
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button")
.contains("Skip table fetch")
.click({ force: true })
})
} else {
cy.intercept("**/tables").as("datasourceTables")
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button")
.contains("Save and fetch tables")
.click({ force: true })
})
// Wait for tables to be fetched
cy.wait("@datasourceTables", { timeout: 60000 })
}
})
Cypress.Commands.add("createRestQuery", (method, restUrl, queryPrettyName) => {
// addExternalDatasource should be called prior to this
// Configures REST datasource & sends query
cy.get(".spectrum-Button", { timeout: 1000 })
.contains("Add query")
.click({ force: true })
// Select Method & add Rest URL
cy.get(".spectrum-Picker-label").eq(1).click()
cy.get(".spectrum-Menu").contains(method).click()
cy.get("input").clear().type(restUrl)
// Send query
cy.get(".spectrum-Button").contains("Send").click({ force: true })
cy.get(".spectrum-Button", { timeout: 500 })
.contains("Save")
.click({ force: true })
cy.get(".hierarchy-items-container")
.should("contain", method)
.and("contain", queryPrettyName)
})
// MISC
Cypress.Commands.add("closeModal", () => {
cy.get(".spectrum-Modal", { timeout: 2000 }).within(() => {
cy.get(".close-icon").click()
})
// Confirm modal has closed
cy.get(".spectrum-Modal", { timeout: 10000 }).should("not.exist")
})
Cypress.Commands.add("expandBudibaseConnection", () => {
if (Cypress.$(".nav-item > .content > .opened").length === 0) {
// expand the Budibase DB connection string
cy.get(".icon.arrow").eq(0).click()
}
})

View file

@ -1,3 +0,0 @@
Cypress.Cookies.defaults({
preserve: "budibase:auth",
})

View file

@ -1,16 +0,0 @@
const filterTests = (testTags, runTest) => {
// testTags is an array of tags
// runTest is all tests
if (Cypress.env("TEST_TAGS")) {
const tags = Cypress.env("TEST_TAGS").split("/")
const found = testTags.some($testTags => tags.includes($testTags))
if (found) {
runTest()
}
} else {
runTest()
}
}
export default filterTests

View file

@ -1,22 +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"
import "./cookies"
// Alternatively you can use CommonJS syntax:
// require('./commands')
require("cypress-terminal-report/src/installLogsCollector")()

View file

@ -1,136 +0,0 @@
// createApp test
export const CREATE_APP_BUTTON = '[data-cy="create-app-btn"]'
export const TEMPLATE_CATEGORY_FILTER = ".template-category-filters"
export const TEMPLATE_CATEGORY = ".template-categories"
export const APP_TABLE = ".appTable"
export const SPECTRUM_BUTTON_TEMPLATE = ".spectrum-Button"
export const TEMPLATE_CATEGORY_ACTIONGROUP = ".template-category"
export const TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON =
".template-category-filters .spectrum-ActionButton"
export const SPECTRUM_MODAL = ".spectrum-Modal"
export const APP_NAME_INPUT = "input" // we need to update this with atribute cy-data;
export const SPECTRUM_BUTTON_GROUP = ".spectrum-ButtonGroup"
export const SPECTRUM_MODAL_INPUT = ".spectrum-Modal input"
//AddMultiOptionDatatype
export const CATEGORY_DATA = '[data-cy="category-Data"]'
export const COMPONENT_DATA_PROVIDER = '[data-cy="component-Data Provider"]'
export const DATASOURCE_PROP_CONTROL = '[data-cy="dataSource-prop-control"]'
export const DROPDOWN = ".dropdown"
export const SPECTRUM_PICKER_LABEL = ".spectrum-Picker-label"
export const DATASOURCE_FIELD_CONTROL = '[data-cy="field-prop-control"]'
export const OPTION_TYPE_PROP_CONTROL = '[data-cy="optionsType-prop-control'
//AddRadioButtons
export const SPECTRUM_POPOVER = ".spectrum-Popover"
export const OPTION_SOURCE_PROP_CONROL = '[data-cy="optionsSource-prop-control'
export const APP_TABLE_STATUS = ".appTable .app-status"
export const APP_TABLE_ROW_ACTION = ".appTable .app-row-actions"
export const APP_TABLE_APP_NAME = '[data-cy="app-name-link"]'
export const DEPLOYMENT_TOP_NAV_GLOBESTRIKE =
".deployment-top-nav svg[aria-label=GlobeStrike]"
export const DEPLOYMENT_TOP_GLOBE = ".deployment-top-nav svg[aria-label=Globe]"
export const PUBLISH_POPOVER_MENU = '[data-cy="publish-popover-menu"]'
export const PUBLISH_POPOVER_ACTION = '[data-cy="publish-popover-action"]'
export const PUBLISH_POPOVER_MESSAGE = ".publish-popover-message"
export const SPECTRUM_BUTTON = ".spectrum-Button"
export const SPECTRUM_LINK = ".spectrum-Link"
export const TOPRIGHTNAV_BUTTON_SPECTRUM = ".toprightnav button.spectrum-Button"
//createComponents
export const SETTINGS = "[data-cy=Settings]"
export const SETTINGS_INPUT = "[data-cy=setting-text] input"
export const DESIGN = "[data-cy=Design]"
export const FONT_SIZE_PROP_CONTROL = "[data-cy=font-size-prop-control]"
export const DATA_CY_DATASOURCE = "[data-cy=setting-dataSource]"
export const DROPDOWN_CONTAINER = ".dropdown-container"
export const SPECTRUM_PICKER = ".spectrum-Picker"
//autoScreens
export const LABEL_ADD_CIRCLE = "[aria-label=AddCircle]"
export const ITEM_DISABLED = ".item.disabled"
export const CONFIRM_WRAP_SPE_BUTTON = ".confirm-wrap .spectrum-Button"
export const DATA_SOURCE_ENTRY = ".data-source-entry"
export const BODY = ".body"
//publishWorkFlow
export const DEPLOY_APP_MODAL = ".spectrum-Modal [data-cy=deploy-app-modal]"
export const DEPLOY_SUCCESS_MODAL =
".spectrum-Modal [data-cy=deploy-app-success-modal]"
export const DEPLOY_APP_URL_INPUT = "[data-cy=deployed-app-url] input"
export const GLOBESTRIKE = "svg[aria-label=GlobeStrike]"
export const GLOBE = "svg[aria-label=Globe]"
export const UNPUBLISH_MODAL = "[data-cy=unpublish-modal]"
export const CONFIRM_WRAP_BUTTON = ".confirm-wrap button"
export const DEPLOYMENT_TOP_NAV = ".deployment-top-nav"
//changeAppiconAndColour
export const APP_ROW_ACTION = ".app-row-actions-icon"
export const SPECTRUM_MENU = ".spectrum-Menu"
export const ICON_ITEM = ".icon-item"
export const FILL = ".fill"
export const COLOURSS = ".colors"
export const AREA_LABEL = "[aria-label]"
export const TITLE = ".title"
export const GRID = ".grid"
export const COLOUR = ".color"
//createAutomation
export const ADD_BUTTON_SPECTRUM = ".add-button .spectrum-Icon"
export const MODAL_INNER_WRAPPER = ".modal-inner-wrapper"
export const SPECTRUM_BUTTON_CTA = ".spectrum-Button--cta"
export const SPECTRUM_TEXTFIELD_INPUT = ".spectrum-Textfield-input"
//createTable
export const TABLE_TITLE_H1 = ".table-title h1"
export const TABLE_TITLE = ".title"
export const SPECTRUM_TABLE_EDIT = ".spectrum-Table-editIcon > use"
export const SPECTRUM_SWITCH_INPUT = ".spectrum-Switch-input"
export const SPECTRUM_CHECKBOX_INPUT = ".spectrum-Checkbox-input"
export const SPECTRUM_PAGINATION = ".spectrum-Pagination"
export const SPECTRUM_ACTION_BUTTON = ".spectrum-ActionButton"
export const SPECTRUM_BODY_SECOND = ".spectrum-Body--secondary"
export const POPOVERS = ".popovers"
export const SPECTRUM_DIALOG_GRID = ".spectrum-Dialog-grid"
export const DELETE_COLUMN_CONFIRM = '[data-cy="delete-column-confirm"]'
export const NAV_ITEM = ".nav-item"
export const ACTION_SPECTRUM_ICON = ".actions .spectrum-Icon"
export const SPECTRUM_MENU_CHILD2 = ".spectrum-Menu > :nth-child(2)"
export const DELETE_TABLE_CONFIRM = '[data-cy="delete-table-confirm"]'
//adminAndManagement Folder
export const SPECTRUM_TABLE = ".spectrum-Table"
export const SPECTRUM_SIDENAV = ".spectrum-SideNav"
export const SPECTRUM_TABLE_ROW = ".spectrum-Table-row"
export const SPECTRUM_TABLE_CELL = ".spectrum-Table-cell"
export const FIELD = ".field"
export const CONTAINER = ".container"
export const REGENERATE = ".regenerate"
export const SPECTRUM_DIALOG_CONTENT = ".spectrum-Dialog-content"
export const SPECTRUM_ICON = ".spectrum-Icon"
export const SPECTRUM_HEADING = ".spectrum-Heading"
export const SPECTRUM_FORM_ITEMFIELD = ".spectrum-Form-itemField"
export const LIST_ITEMS = ".list-items"
//createView
export const SPECTRUM_MENU_ITEM_LABEL = ".spectrum-Menu-itemLabel"
//revertApp
export const TOP_RIGHT_NAV = ".toprightnav"
export const AREA_LABEL_REVERT = "[aria-label=Revert]"
export const ROOT = ".root"
//queryLevelTransformers
export const SPECTRUM_TABS_ITEM = ".spectrum-Tabs-itemLabel"
export const CODEMIRROR_TEXTAREA = ".CodeMirror textarea"
//renameApplication
export const WRAPPER = ".wrapper"
export const ERROR = ".error"
export const AREA_LABEL_MORE = "[aria-label=More]"
export const APP_ROW_ACTION_MENU_POPOVER =
'[data-cy="app-row-actions-menu-popover"]'
export const SPECTRUM_MENU_ITEM = ".spectrum-Menu-item"
//commands
export const HOME_LOGO = ".home-logo"

View file

@ -1,14 +0,0 @@
/* eslint-disable */
const breweries = data
const totals = {}
for (let brewery of breweries)
{const state = brewery.state
if (totals[state] == null)
{totals[state] = 1
} else
{totals[state]++
}
}
const entries = Object.entries(totals)
return entries.map(([state, count]) => ({ state, count }))

View file

@ -1,31 +0,0 @@
/* eslint-disable */
const breweries = data
const totals = {}
for (let brewery of breweries)
{const state = brewery.state
if (totals[state] == null)
{totals[state] = 1
} else
{totals[state]++
}
}
const stateCodes =
{texas: "tx",
colorado: "co",
florida: "fl",
iwoa: "ia",
louisiana: "la",
california: "ca",
pennsylvania: "pa",
georgia: "ga",
"new hampshire": "nh",
virginia: "va",
michigan: "mi",
maryland: "md",
ohio: "oh",
}
const entries = Object.entries(totals)
return entries.map(([state, count]) =>
{stateCodes[state.toLowerCase()]
return { state, count, flag: "http://flags.ox3.in/svg/us/${stateCode}.svg" }
})

View file

@ -1,4 +0,0 @@
// @ts-ignore
import { run } from "../setup"
run("../../server/src/index", "../../worker/src/index")

View file

@ -1,6 +1,6 @@
{
"name": "@budibase/builder",
"version": "2.2.12-alpha.54",
"version": "2.2.12-alpha.69",
"license": "GPL-3.0",
"private": true,
"scripts": {
@ -9,20 +9,7 @@
"dev:builder": "routify -c dev:vite",
"dev:vite": "vite --host 0.0.0.0",
"rollup": "rollup -c -w",
"test": "jest",
"cy:setup": "ts-node ./cypress/ts/setup.ts",
"cy:setup:ci": "node ./cypress/setup.js",
"cy:open": "cypress open",
"cy:run": "cypress run",
"cy:run:ci": "cypress run --headed --browser chrome --spec cypress/integration/createApp.spec.js",
"cy:run:ci:record": "xvfb-run cypress run --headed --browser chrome --record",
"cy:test": "start-server-and-test cy:setup http://localhost:4100/builder cy:run",
"cy:ci": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci",
"cy:ci:record": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci:record; npm run cy:ci:report",
"cy:ci:report": "mochawesome-merge cypress/reports/*.json > cypress/reports/testReport.json && marge cypress/reports/testReport.json --reportDir cypress/reports --inline",
"cy:ci:notify": "node scripts/cypressResultsWebhook",
"cy:debug": "start-server-and-test cy:setup http://localhost:4100/builder cy:open",
"cy:debug:ci": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:open"
"test": "jest"
},
"jest": {
"globals": {
@ -71,10 +58,10 @@
}
},
"dependencies": {
"@budibase/bbui": "2.2.12-alpha.54",
"@budibase/client": "2.2.12-alpha.54",
"@budibase/frontend-core": "2.2.12-alpha.54",
"@budibase/string-templates": "2.2.12-alpha.54",
"@budibase/bbui": "2.2.12-alpha.69",
"@budibase/client": "2.2.12-alpha.69",
"@budibase/frontend-core": "2.2.12-alpha.69",
"@budibase/string-templates": "2.2.12-alpha.69",
"@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",

View file

@ -1,14 +1,14 @@
const cypressConfig = require("../cypress.json")
const testConfig = require("./testConfig.json")
// normal development system
const SERVER_PORT = cypressConfig.env.PORT
const WORKER_PORT = cypressConfig.env.WORKER_PORT
const SERVER_PORT = testConfig.env.PORT
const WORKER_PORT = testConfig.env.WORKER_PORT
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = "cypress"
}
process.env.ENABLE_ANALYTICS = "0"
process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET
process.env.JWT_SECRET = testConfig.env.JWT_SECRET
process.env.SELF_HOSTED = 1
process.env.WORKER_URL = `http://localhost:${WORKER_PORT}/`
process.env.APPS_URL = `http://localhost:${SERVER_PORT}/`
@ -23,10 +23,7 @@ process.env.ALLOW_DEV_AUTOMATIONS = 1
// Stop info logs polluting test outputs
process.env.LOG_LEVEL = "error"
exports.run = (
serverLoc = "../../server/dist",
workerLoc = "../../worker/dist"
) => {
exports.run = (serverLoc = "../server/dist", workerLoc = "../worker/dist") => {
// require("dotenv").config({ path: resolve(dir, ".env") })
// don't make this a variable or top level require
// it will cause environment module to be loaded prematurely

View file

@ -509,21 +509,24 @@ const getSelectedRowsBindings = asset => {
return bindings
}
export const makeStateBinding = key => {
return {
type: "context",
runtimeBinding: `${makePropSafe("state")}.${makePropSafe(key)}`,
readableBinding: `State.${key}`,
category: "State",
icon: "AutomatedSegment",
display: { name: key },
}
}
/**
* Gets all state bindings that are globally available.
*/
const getStateBindings = () => {
let bindings = []
if (get(store).clientFeatures?.state) {
const safeState = makePropSafe("state")
bindings = getAllStateVariables().map(key => ({
type: "context",
runtimeBinding: `${safeState}.${makePropSafe(key)}`,
readableBinding: `State.${key}`,
category: "State",
icon: "AutomatedSegment",
display: { name: key },
}))
bindings = getAllStateVariables().map(makeStateBinding)
}
return bindings
}

View file

@ -42,13 +42,7 @@
</script>
{#if type === "options" && meta.constraints.inclusion.length !== 0}
<Select
{label}
data-cy="{meta.name}-select"
bind:value
options={meta.constraints.inclusion}
sort
/>
<Select {label} bind:value options={meta.constraints.inclusion} sort />
{:else if type === "datetime"}
<DatePicker
{error}
@ -61,7 +55,7 @@
{:else if type === "attachment"}
<Dropzone {label} {error} bind:value />
{:else if type === "boolean"}
<Toggle text={label} {error} bind:value data-cy="{meta.name}-input" />
<Toggle text={label} {error} bind:value />
{:else if type === "array" && meta.constraints.inclusion.length !== 0}
<Multiselect
bind:value
@ -87,12 +81,5 @@
{error}
/>
{:else}
<Input
{label}
data-cy="{meta.name}-input"
{type}
{error}
bind:value
disabled={readonly}
/>
<Input {label} {type} {error} bind:value disabled={readonly} />
{/if}

View file

@ -546,9 +546,5 @@
Your data will be deleted and this action cannot be undone - enter the column
name to confirm.
</p>
<Input
dataCy="delete-column-confirm"
bind:value={deleteColName}
placeholder={originalName}
/>
<Input bind:value={deleteColName} placeholder={originalName} />
</ConfirmDialog>

View file

@ -119,7 +119,6 @@
selection is always undefined -->
<Select
label="Role"
data-cy="roleId-select"
bind:value={row.roleId}
options={$roles}
getOptionLabel={role => role.name}

View file

@ -13,7 +13,7 @@
}
</script>
<Button data-cy="edit-row" secondary small on:click={showModal}>Edit</Button>
<Button secondary small on:click={showModal}>Edit</Button>
<Modal bind:this={modal}>
<svelte:component this={modalContentComponent} {row} />
</Modal>

View file

@ -177,7 +177,7 @@
<EnvDropdown
showModal={() => showModal(configKey)}
variables={$environment.variables}
type={schema[configKey].type}
type={configKey === "port" ? "string" : schema[configKey].type}
on:change
bind:value={config[configKey]}
error={$validation.errors[configKey]}

View file

@ -82,7 +82,7 @@
let displayString
if (throughTableName) {
displayString = `${fromTableName} through ${throughTableName} ${toTableName}`
displayString = `${fromTableName} ${toTableName}`
} else {
displayString = `${fromTableName} → ${toTableName}`
}

View file

@ -47,7 +47,6 @@
disabled={error || !name || !datasource?.type}
>
<Input
data-cy="datasource-name-input"
label="Datasource Name"
on:input={checkValid}
bind:value={name}

View file

@ -10,17 +10,17 @@
} from "@budibase/bbui"
import { tables } from "stores/backend"
import { Helpers } from "@budibase/bbui"
import { RelationshipErrorChecker } from "./relationshipErrors"
import { onMount } from "svelte"
export let save
export let datasource
export let plusTables = []
export let fromRelationship = {}
export let toRelationship = {}
export let selectedFromTable
export let close
const colNotSet = "Please specify a column name"
const relationshipAlreadyExists =
"A relationship between these tables already exists."
const relationshipTypes = [
{
label: "One to Many",
@ -42,63 +42,28 @@
)
let tableOptions
let errorChecker = new RelationshipErrorChecker(
invalidThroughTable,
relationshipExists
)
let errors = {}
let hasClickedSave = !!fromRelationship.relationshipType
let fromPrimary,
fromForeign,
fromTable,
toTable,
throughTable,
fromColumn,
toColumn
let fromPrimary, fromForeign, fromColumn, toColumn
let fromId, toId, throughId, throughToKey, throughFromKey
let isManyToMany, isManyToOne, relationshipType
$: {
if (!fromPrimary) {
fromPrimary = fromRelationship.foreignKey
fromForeign = toRelationship.foreignKey
}
if (!fromColumn && !errors.fromColumn) {
fromColumn = toRelationship.name
}
if (!toColumn && !errors.toColumn) {
toColumn = fromRelationship.name
}
if (!fromId) {
fromId = toRelationship.tableId
}
if (!toId) {
toId = fromRelationship.tableId
}
if (!throughId) {
throughId = fromRelationship.through
throughFromKey = fromRelationship.throughFrom
throughToKey = fromRelationship.throughTo
}
if (!relationshipType) {
relationshipType = fromRelationship.relationshipType
}
}
let hasValidated = false
$: tableOptions = plusTables.map(table => ({
label: table.name,
value: table._id,
}))
$: valid = getErrorCount(errors) === 0 || !hasClickedSave
$: valid = getErrorCount(errors) === 0 && allRequiredAttributesSet()
$: isManyToMany = relationshipType === RelationshipTypes.MANY_TO_MANY
$: isManyToOne = relationshipType === RelationshipTypes.MANY_TO_ONE
$: fromTable = plusTables.find(table => table._id === fromId)
$: toTable = plusTables.find(table => table._id === toId)
$: throughTable = plusTables.find(table => table._id === throughId)
$: toRelationship.relationshipType = fromRelationship?.relationshipType
const getErrorCount = errors =>
Object.entries(errors)
.filter(entry => !!entry[1])
.map(entry => entry[0]).length
function getTable(id) {
return plusTables.find(table => table._id === id)
}
function invalidThroughTable() {
// need to know the foreign key columns to check error
@ -116,93 +81,103 @@
}
return false
}
function validate() {
const isMany = relationshipType === RelationshipTypes.MANY_TO_MANY
const tableNotSet = "Please specify a table"
const foreignKeyNotSet = "Please pick a foreign key"
const errObj = {}
if (!relationshipType) {
errObj.relationshipType = "Please specify a relationship type"
}
if (!fromTable) {
errObj.fromTable = tableNotSet
}
if (!toTable) {
errObj.toTable = tableNotSet
}
if (isMany && !throughTable) {
errObj.throughTable = tableNotSet
}
if (isMany && !throughFromKey) {
errObj.throughFromKey = foreignKeyNotSet
}
if (isMany && !throughToKey) {
errObj.throughToKey = foreignKeyNotSet
}
if (invalidThroughTable()) {
errObj.throughTable =
"Ensure non-key columns are nullable or auto-generated"
}
if (!isMany && !fromForeign) {
errObj.fromForeign = foreignKeyNotSet
}
if (!fromColumn) {
errObj.fromColumn = colNotSet
}
if (!toColumn) {
errObj.toColumn = colNotSet
}
if (!isMany && !fromPrimary) {
errObj.fromPrimary = "Please pick the primary key"
}
if (isMany && relationshipExists()) {
errObj.fromTable = relationshipAlreadyExists
errObj.toTable = relationshipAlreadyExists
}
// currently don't support relationships back onto the table itself, needs to relate out
const tableError = "From/to/through tables must be different"
if (fromTable && (fromTable === toTable || fromTable === throughTable)) {
errObj.fromTable = tableError
}
if (toTable && (toTable === fromTable || toTable === throughTable)) {
errObj.toTable = tableError
}
function relationshipExists() {
if (
throughTable &&
(throughTable === fromTable || throughTable === toTable)
originalFromTable &&
originalToTable &&
originalFromTable === getTable(fromId) &&
originalToTable === getTable(toId)
) {
errObj.throughTable = tableError
}
const colError = "Column name cannot be an existing column"
if (isColumnNameBeingUsed(toTable, fromColumn, originalFromColumnName)) {
errObj.fromColumn = colError
}
if (isColumnNameBeingUsed(fromTable, toColumn, originalToColumnName)) {
errObj.toColumn = colError
}
let fromType, toType
if (fromPrimary && fromForeign) {
fromType = fromTable?.schema[fromPrimary]?.type
toType = toTable?.schema[fromForeign]?.type
}
if (fromType && toType && fromType !== toType) {
errObj.fromForeign =
"Column type of the foreign key must match the primary key"
}
errors = errObj
return getErrorCount(errors) === 0
}
function isColumnNameBeingUsed(table, columnName, originalName) {
if (!table || !columnName || columnName === originalName) {
return false
}
const keys = Object.keys(table.schema).map(key => key.toLowerCase())
return keys.indexOf(columnName.toLowerCase()) !== -1
let fromThroughLinks = Object.values(
datasource.entities[getTable(fromId).name].schema
).filter(value => value.through)
let toThroughLinks = Object.values(
datasource.entities[getTable(toId).name].schema
).filter(value => value.through)
const matchAgainstUserInput = (fromTableId, toTableId) =>
(fromTableId === fromId && toTableId === toId) ||
(fromTableId === toId && toTableId === fromId)
return !!fromThroughLinks.find(from =>
toThroughLinks.find(
to =>
from.through === to.through &&
matchAgainstUserInput(from.tableId, to.tableId)
)
)
}
function getErrorCount(errors) {
return Object.entries(errors).filter(entry => !!entry[1]).length
}
function allRequiredAttributesSet() {
const base = getTable(fromId) && getTable(toId) && fromColumn && toColumn
if (relationshipType === RelationshipTypes.MANY_TO_ONE) {
return base && fromPrimary && fromForeign
} else {
return base && getTable(throughId) && throughFromKey && throughToKey
}
}
function validate() {
if (!allRequiredAttributesSet() && !hasValidated) {
return
}
hasValidated = true
errorChecker.setType(relationshipType)
const fromTable = getTable(fromId),
toTable = getTable(toId),
throughTable = getTable(throughId)
errors = {
relationshipType: errorChecker.relationshipTypeSet(relationshipType),
fromTable:
errorChecker.tableSet(fromTable) ||
errorChecker.doesRelationshipExists() ||
errorChecker.differentTables(fromId, toId, throughId),
toTable:
errorChecker.tableSet(toTable) ||
errorChecker.doesRelationshipExists() ||
errorChecker.differentTables(toId, fromId, throughId),
throughTable:
errorChecker.throughTableSet(throughTable) ||
errorChecker.throughIsNullable() ||
errorChecker.differentTables(throughId, fromId, toId),
throughFromKey:
errorChecker.manyForeignKeySet(throughFromKey) ||
errorChecker.manyTypeMismatch(
fromTable,
throughTable,
fromTable.primary[0],
throughFromKey
),
throughToKey:
errorChecker.manyForeignKeySet(throughToKey) ||
errorChecker.manyTypeMismatch(
toTable,
throughTable,
toTable.primary[0],
throughToKey
),
fromForeign:
errorChecker.foreignKeySet(fromForeign) ||
errorChecker.typeMismatch(fromTable, toTable, fromPrimary, fromForeign),
fromPrimary: errorChecker.primaryKeySet(fromPrimary),
fromColumn: errorChecker.columnBeingUsed(
toTable,
fromColumn,
originalFromColumnName
),
toColumn: errorChecker.columnBeingUsed(
fromTable,
toColumn,
originalToColumnName
),
}
return getErrorCount(errors) === 0
}
function buildRelationships() {
@ -243,13 +218,13 @@
if (manyToMany) {
relateFrom = {
...relateFrom,
through: throughTable._id,
fieldName: toTable.primary[0],
through: getTable(throughId)._id,
fieldName: getTable(toId).primary[0],
}
relateTo = {
...relateTo,
through: throughTable._id,
fieldName: fromTable.primary[0],
through: getTable(throughId)._id,
fieldName: getTable(fromId).primary[0],
throughFrom: relateFrom.throughTo,
throughTo: relateFrom.throughFrom,
}
@ -277,35 +252,6 @@
toRelationship = relateTo
}
function relationshipExists() {
if (
originalFromTable &&
originalToTable &&
originalFromTable === fromTable &&
originalToTable === toTable
) {
return false
}
let fromThroughLinks = Object.values(
datasource.entities[fromTable.name].schema
).filter(value => value.through)
let toThroughLinks = Object.values(
datasource.entities[toTable.name].schema
).filter(value => value.through)
const matchAgainstUserInput = (fromTableId, toTableId) =>
(fromTableId === fromId && toTableId === toId) ||
(fromTableId === toId && toTableId === fromId)
return !!fromThroughLinks.find(from =>
toThroughLinks.find(
to =>
from.through === to.through &&
matchAgainstUserInput(from.tableId, to.tableId)
)
)
}
function removeExistingRelationship() {
if (originalFromTable && originalFromColumnName) {
delete datasource.entities[originalFromTable.name].schema[
@ -320,7 +266,6 @@
}
async function saveRelationship() {
hasClickedSave = true
if (!validate()) {
return false
}
@ -328,10 +273,10 @@
removeExistingRelationship()
// source of relationship
datasource.entities[fromTable.name].schema[fromRelationship.name] =
datasource.entities[getTable(fromId).name].schema[fromRelationship.name] =
fromRelationship
// save other side of relationship in the other schema
datasource.entities[toTable.name].schema[toRelationship.name] =
datasource.entities[getTable(toId).name].schema[toRelationship.name] =
toRelationship
await save()
@ -342,6 +287,36 @@
await tables.fetch()
close()
}
function changed(fn) {
if (typeof fn === "function") {
fn()
}
validate()
}
onMount(() => {
if (fromRelationship) {
fromPrimary = fromRelationship.foreignKey
toId = fromRelationship.tableId
throughId = fromRelationship.through
throughFromKey = fromRelationship.throughFrom
throughToKey = fromRelationship.throughTo
toColumn = fromRelationship.name
}
if (toRelationship) {
fromForeign = toRelationship.foreignKey
fromId = toRelationship.tableId
fromColumn = toRelationship.name
}
relationshipType =
fromRelationship.relationshipType || RelationshipTypes.MANY_TO_ONE
if (selectedFromTable) {
fromId = selectedFromTable._id
fromColumn = selectedFromTable.name
fromPrimary = selectedFromTable?.primary[0] || null
}
})
</script>
<ModalContent
@ -355,34 +330,35 @@
options={relationshipTypes}
bind:value={relationshipType}
bind:error={errors.relationshipType}
on:change={() => (errors.relationshipType = null)}
on:change={() =>
changed(() => {
hasValidated = false
})}
/>
<div class="headings">
<Detail>Tables</Detail>
</div>
<Select
label="Select from table"
options={tableOptions}
bind:value={fromId}
bind:error={errors.fromTable}
on:change={e => {
fromColumn = tableOptions.find(opt => opt.value === e.detail)?.label || ""
if (errors.fromTable === relationshipAlreadyExists) {
errors.toColumn = null
}
errors.fromTable = null
errors.fromColumn = null
errors.toTable = null
errors.throughTable = null
}}
/>
{#if isManyToOne && fromTable}
{#if !selectedFromTable}
<Select
label={`Primary Key (${fromTable.name})`}
options={Object.keys(fromTable.schema)}
label="Select from table"
options={tableOptions}
bind:value={fromId}
bind:error={errors.fromTable}
on:change={e =>
changed(() => {
const table = plusTables.find(tbl => tbl._id === e.detail)
fromColumn = table?.name || ""
fromPrimary = table?.primary?.[0]
})}
/>
{/if}
{#if isManyToOne && fromId}
<Select
label={`Primary Key (${getTable(fromId).name})`}
options={Object.keys(getTable(fromId).schema)}
bind:value={fromPrimary}
bind:error={errors.fromPrimary}
on:change={() => (errors.fromPrimary = null)}
on:change={changed}
/>
{/if}
<Select
@ -390,16 +366,12 @@
options={tableOptions}
bind:value={toId}
bind:error={errors.toTable}
on:change={e => {
toColumn = tableOptions.find(opt => opt.value === e.detail)?.label || ""
if (errors.toTable === relationshipAlreadyExists) {
errors.fromColumn = null
}
errors.toTable = null
errors.toColumn = null
errors.fromTable = null
errors.throughTable = null
}}
on:change={e =>
changed(() => {
const table = plusTables.find(tbl => tbl._id === e.detail)
toColumn = table.name || ""
fromForeign = null
})}
/>
{#if isManyToMany}
<Select
@ -407,45 +379,45 @@
options={tableOptions}
bind:value={throughId}
bind:error={errors.throughTable}
on:change={() => {
errors.fromTable = null
errors.toTable = null
errors.throughTable = null
}}
on:change={() =>
changed(() => {
throughToKey = null
throughFromKey = null
})}
/>
{#if fromTable && toTable && throughTable}
{#if fromId && toId && throughId}
<Select
label={`Foreign Key (${fromTable?.name})`}
options={Object.keys(throughTable?.schema)}
label={`Foreign Key (${getTable(fromId)?.name})`}
options={Object.keys(getTable(throughId)?.schema)}
bind:value={throughToKey}
bind:error={errors.throughToKey}
on:change={e => {
if (throughFromKey === e.detail) {
throughFromKey = null
}
errors.throughToKey = null
}}
on:change={e =>
changed(() => {
if (throughFromKey === e.detail) {
throughFromKey = null
}
})}
/>
<Select
label={`Foreign Key (${toTable?.name})`}
options={Object.keys(throughTable?.schema)}
label={`Foreign Key (${getTable(toId)?.name})`}
options={Object.keys(getTable(throughId)?.schema)}
bind:value={throughFromKey}
bind:error={errors.throughFromKey}
on:change={e => {
if (throughToKey === e.detail) {
throughToKey = null
}
errors.throughFromKey = null
}}
on:change={e =>
changed(() => {
if (throughToKey === e.detail) {
throughToKey = null
}
})}
/>
{/if}
{:else if isManyToOne && toTable}
{:else if isManyToOne && toId}
<Select
label={`Foreign Key (${toTable?.name})`}
options={Object.keys(toTable?.schema)}
label={`Foreign Key (${getTable(toId)?.name})`}
options={Object.keys(getTable(toId)?.schema)}
bind:value={fromForeign}
bind:error={errors.fromForeign}
on:change={() => (errors.fromForeign = null)}
on:change={changed}
/>
{/if}
<div class="headings">
@ -459,15 +431,13 @@
label="From table column"
bind:value={fromColumn}
bind:error={errors.fromColumn}
on:change={e => {
errors.fromColumn = e.detail?.length > 0 ? null : colNotSet
}}
on:change={changed}
/>
<Input
label="To table column"
bind:value={toColumn}
bind:error={errors.toColumn}
on:change={e => (errors.toColumn = e.detail?.length > 0 ? null : colNotSet)}
on:change={changed}
/>
<div slot="footer">
{#if originalFromColumnName != null}

View file

@ -0,0 +1,103 @@
import { RelationshipTypes } from "constants/backend"
const typeMismatch = "Column type of the foreign key must match the primary key"
const columnBeingUsed = "Column name cannot be an existing column"
const mustBeDifferentTables = "From/to/through tables must be different"
const primaryKeyNotSet = "Please pick the primary key"
const throughNotNullable =
"Ensure non-key columns are nullable or auto-generated"
const noRelationshipType = "Please specify a relationship type"
const tableNotSet = "Please specify a table"
const foreignKeyNotSet = "Please pick a foreign key"
const relationshipAlreadyExists =
"A relationship between these tables already exists"
function isColumnNameBeingUsed(table, columnName, originalName) {
if (!table || !columnName || columnName === originalName) {
return false
}
const keys = Object.keys(table.schema).map(key => key.toLowerCase())
return keys.indexOf(columnName.toLowerCase()) !== -1
}
function typeMismatchCheck(fromTable, toTable, primary, foreign) {
let fromType, toType
if (primary && foreign) {
fromType = fromTable?.schema[primary]?.type
toType = toTable?.schema[foreign]?.type
}
return fromType && toType && fromType !== toType ? typeMismatch : null
}
export class RelationshipErrorChecker {
constructor(invalidThroughTableFn, relationshipExistsFn) {
this.invalidThroughTable = invalidThroughTableFn
this.relationshipExists = relationshipExistsFn
}
setType(type) {
this.type = type
}
isMany() {
return this.type === RelationshipTypes.MANY_TO_MANY
}
relationshipTypeSet(type) {
return !type ? noRelationshipType : null
}
tableSet(table) {
return !table ? tableNotSet : null
}
throughTableSet(table) {
return this.isMany() && !table ? tableNotSet : null
}
manyForeignKeySet(key) {
return this.isMany() && !key ? foreignKeyNotSet : null
}
foreignKeySet(key) {
return !this.isMany() && !key ? foreignKeyNotSet : null
}
primaryKeySet(key) {
return !this.isMany() && !key ? primaryKeyNotSet : null
}
throughIsNullable() {
return this.invalidThroughTable() ? throughNotNullable : null
}
doesRelationshipExists() {
return this.isMany() && this.relationshipExists()
? relationshipAlreadyExists
: null
}
differentTables(table1, table2, table3) {
// currently don't support relationships back onto the table itself, needs to relate out
const error = table1 && (table1 === table2 || (table3 && table1 === table3))
return error ? mustBeDifferentTables : null
}
columnBeingUsed(table, column, ogName) {
return isColumnNameBeingUsed(table, column, ogName) ? columnBeingUsed : null
}
typeMismatch(fromTable, toTable, primary, foreign) {
if (this.isMany()) {
return null
}
return typeMismatchCheck(fromTable, toTable, primary, foreign)
}
manyTypeMismatch(table, throughTable, primary, foreign) {
if (!this.isMany()) {
return null
}
return typeMismatchCheck(table, throughTable, primary, foreign)
}
}

View file

@ -6,13 +6,7 @@
export let indented
</script>
<div
data-cy="table-nav-item"
class:indented
class:selected
on:click
class={className}
>
<div class:indented class:selected on:click class={className}>
<i class={icon} />
<span>{title}</span>
<slot />

View file

@ -108,7 +108,6 @@
disabled={error || !name || (rows.length && !allValid)}
>
<Input
data-cy="table-name-input"
thin
label="Table Name"
on:input={checkValid}

View file

@ -134,11 +134,7 @@
This action cannot be undone - to continue please enter the table name below
to confirm.
</p>
<Input
bind:value={deleteTableName}
placeholder={table.name}
dataCy="delete-table-confirm"
/>
<Input bind:value={deleteTableName} placeholder={table.name} />
</ConfirmDialog>
<style>

View file

@ -74,7 +74,6 @@
<Modal bind:this={appLockModal}>
<ModalContent
title={lockedByHeading}
dataCy={"app-lock-modal"}
showConfirmButton={false}
showCancelButton={false}
>

View file

@ -9,7 +9,6 @@
export let onCancel = undefined
export let warning = true
export let disabled
export let dataCy = null
let modal
@ -29,7 +28,6 @@
{cancelText}
{warning}
{disabled}
{dataCy}
>
<Body size="S">
{body}

View file

@ -4,12 +4,11 @@
export let title = ""
export let actionIcon
export let action
export let dataCy
$: actionDefined = typeof action === "function"
</script>
<div class="dash-card" data-cy={dataCy}>
<div class="dash-card">
<div class="dash-card-header" class:active={actionDefined} on:click={action}>
<span class="dash-card-title">
<Detail size="M">{title}</Detail>

View file

@ -68,7 +68,7 @@
<div class="template-categories">
<Layout gap="XL" noPadding>
{#each filteredCategories as category}
<div class="template-category" data-cy={category.name}>
<div class="template-category">
<Detail size="M">{category.name}</Detail>
<div class="template-grid">
{#each category.templates as templateEntry}

View file

@ -22,6 +22,7 @@
const dispatch = createEventDispatcher()
let bindingDrawer
let valid = true
$: readableValue = runtimeToReadableBinding(bindings, value)
$: tempValue = readableValue
@ -66,11 +67,7 @@
{error}
/>
{#if !disabled}
<div
class="icon"
on:click={bindingDrawer.show}
data-cy="text-binding-button"
>
<div class="icon" on:click={bindingDrawer.show}>
<Icon size="S" name="FlashOn" />
</div>
{/if}
@ -80,12 +77,15 @@
<svelte:fragment slot="description">
Add the objects on the left to enrich your text.
</svelte:fragment>
<Button cta slot="buttons" on:click={handleClose}>Save</Button>
<Button cta slot="buttons" on:click={handleClose} disabled={!valid}>
Save
</Button>
<svelte:component
this={panel}
slot="body"
value={readableValue}
close={handleClose}
bind:valid
on:change={event => (tempValue = event.detail)}
{bindings}
{allowJS}

View file

@ -3,7 +3,6 @@
export let label = null
export let value
export let dataCy = null
const copyToClipboard = val => {
const dummy = document.createElement("textarea")
@ -16,7 +15,7 @@
}
</script>
<div data-cy={dataCy}>
<div>
<Input readonly {value} {label} />
<div class="icon" on:click={() => copyToClipboard(value)}>
<Icon size="S" name="Copy" />

View file

@ -66,7 +66,6 @@
title="Publish to production"
confirmText="Publish"
onConfirm={publishApp}
dataCy={"deploy-app-modal"}
>
The changes you have made will be published to the production version of the
application.
@ -88,12 +87,7 @@
<!-- Publish complete -->
<Modal bind:this={publishCompleteModal}>
<ModalContent
confirmText="Done"
cancelText="View App"
onCancel={viewApp}
dataCy="deploy-app-success-modal"
>
<ModalContent confirmText="Done" cancelText="View App" onCancel={viewApp}>
<div slot="header" class="app-published-header">
<svg
width="26px"
@ -105,11 +99,7 @@
</svg>
<span class="app-published-header-text">App Published!</span>
</div>
<CopyInput
value={publishedUrl}
label="You can view your app at:"
dataCy="deployed-app-url"
/>
<CopyInput value={publishedUrl} label="You can view your app at:" />
</ModalContent>
</Modal>

View file

@ -124,36 +124,37 @@
bind:this={publishPopover}
align="right"
disabled={!isPublished}
dataCy="publish-popover-menu"
anchor={publishPopoverAnchor}
offset={10}
>
<Layout gap="M">
<Heading size="XS">Your published app</Heading>
<Body size="S">
<span class="publish-popover-message">
{processStringSync(
"Last published {{ duration time 'millisecond' }} ago",
{
time:
new Date().getTime() -
new Date(latestDeployments[0].updatedAt).getTime(),
}
)}
</span>
</Body>
<div class="publish-popover-actions">
<Button
warning={true}
icon="GlobeStrike"
disabled={!isPublished}
on:click={unpublishApp}
dataCy="publish-popover-action"
>
Unpublish
</Button>
<Button cta on:click={viewApp}>View app</Button>
</div>
</Layout>
<div class="popover-content">
<Layout noPadding gap="M">
<Heading size="XS">Your published app</Heading>
<Body size="S">
<span class="publish-popover-message">
{processStringSync(
"Last published {{ duration time 'millisecond' }} ago",
{
time:
new Date().getTime() -
new Date(latestDeployments[0].updatedAt).getTime(),
}
)}
</span>
</Body>
<div class="buttons">
<Button
warning={true}
icon="GlobeStrike"
disabled={!isPublished}
on:click={unpublishApp}
>
Unpublish
</Button>
<Button cta on:click={viewApp}>View app</Button>
</div>
</Layout>
</div>
</Popover>
</div>
{/if}
@ -172,7 +173,6 @@
title="Confirm unpublish"
okText="Unpublish app"
onOk={confirmUnpublishApp}
dataCy={"unpublish-modal"}
>
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>?
</ConfirmDialog>
@ -183,11 +183,8 @@
</div>
<style>
.publish-popover-actions :global([data-cy="publish-popover-action"]) {
margin-right: var(--spacing-s);
}
:global([data-cy="publish-popover-menu"]) {
padding: 10px;
.popover-content {
padding: var(--spacing-xl);
}
.buttons {
display: flex;

View file

@ -33,7 +33,6 @@
hoverable
on:click={revertModal.show}
tooltip="Revert changes"
dataCy="revert-application-topnav"
/>
<Modal bind:this={revertModal}>
<ModalContent

View file

@ -11,7 +11,10 @@
} from "@budibase/bbui"
import { getAvailableActions } from "./index"
import { generate } from "shortid"
import { getEventContextBindings } from "builderStore/dataBinding"
import {
getEventContextBindings,
makeStateBinding,
} from "builderStore/dataBinding"
import { currentAsset, store } from "builderStore"
const flipDurationMs = 150
@ -52,7 +55,7 @@
actions,
selectedAction?.id
)
$: allBindings = eventContexBindings.concat(bindings)
$: allBindings = getAllBindings(bindings, eventContexBindings, actions)
$: {
// Ensure each action has a unique ID
if (actions) {
@ -111,6 +114,37 @@
function handleDndFinalize(e) {
actions = e.detail.items
}
const getAllBindings = (bindings, eventContextBindings, actions) => {
let allBindings = eventContextBindings.concat(bindings)
if (!actions) {
return []
}
// Ensure bindings are generated for all "update state" action keys
actions
.filter(action => {
// Find all "Update State" actions which set values
return (
action[EVENT_TYPE_KEY] === "Update State" &&
action.parameters?.type === "set" &&
action.parameters.key
)
})
.forEach(action => {
// Check we have a binding for this action, and generate one if not
const stateBinding = makeStateBinding(action.parameters.key)
const hasKey = allBindings.some(binding => {
return binding.runtimeBinding === stateBinding.runtimeBinding
})
if (!hasKey) {
allBindings.push(stateBinding)
}
})
return allBindings
}
</script>
<DrawerContent>
@ -186,7 +220,7 @@
<div class="selected-action-container">
<svelte:component
this={selectedActionComponent}
parameters={selectedAction.parameters}
bind:parameters={selectedAction.parameters}
bindings={allBindings}
{nested}
/>

View file

@ -193,11 +193,7 @@
</Drawer>
{/if}
</div>
<Popover
bind:this={dropdownRight}
anchor={anchorRight}
dataCy={`dataSource-popover-${$store.selectedComponentId}`}
>
<Popover bind:this={dropdownRight} anchor={anchorRight}>
<div class="dropdown">
<div class="title">
<Heading size="XS">Tables</Heading>

View file

@ -121,12 +121,7 @@
{displayValue}
</ActionButton>
</div>
<Popover
bind:this={dropdown}
on:open={setSelectedUI}
anchor={buttonAnchor}
dataCy="icon-popover"
>
<Popover bind:this={dropdown} on:open={setSelectedUI} anchor={buttonAnchor}>
<div class="container">
<div class="search-area">
<div class="alphabet-area">

View file

@ -74,17 +74,13 @@
})
</script>
<div
class="property-control"
class:highlighted={highlighted && nullishValue}
data-cy={`setting-${key}`}
>
<div class="property-control" class:highlighted={highlighted && nullishValue}>
{#if type !== "boolean" && label}
<div class="label">
<Label>{label}</Label>
</div>
{/if}
<div data-cy={`${key}-prop-control`} class="control">
<div id={`${key}-prop-control`} class="control">
<svelte:component
this={control}
{componentInstance}

View file

@ -22,6 +22,8 @@
let developmentValue
let useProductionValue = true
const HasSpacesRegex = /[\\"\s]/
const deleteVariable = async name => {
try {
await environment.deleteVariable(name)
@ -47,10 +49,16 @@
</script>
<ModalContent
disabled={HasSpacesRegex.test(name)}
onConfirm={() => saveVariable()}
title={!row ? "Add new environment variable" : "Edit environment variable"}
>
<Input disabled={row} label="Name" bind:value={name} />
<Input
disabled={row}
label="Name"
bind:value={name}
error={HasSpacesRegex.test(name) && "Must not include spaces"}
/>
<div>
<Heading size="XS">Production</Heading>
<Input

View file

@ -108,59 +108,62 @@
}
</script>
{#key tourStepKey}
<Popover
align={tourStep?.align}
bind:this={popover}
anchor={popoverAnchor}
dataCy="tour-popover-menu"
maxWidth={300}
dismissible={false}
>
<Layout gap="M">
<div class="tour-header">
<Heading size="XS">{tourStep?.title || "-"}</Heading>
<div>{`${tourStepIdx + 1}/${tourSteps?.length}`}</div>
{#if tourKey}
{#key tourStepKey}
<Popover
align={tourStep?.align}
bind:this={popover}
anchor={popoverAnchor}
maxWidth={300}
dismissible={false}
offset={15}
>
<div class="tour-content">
<Layout noPadding gap="M">
<div class="tour-header">
<Heading size="XS">{tourStep?.title || "-"}</Heading>
<div>{`${tourStepIdx + 1}/${tourSteps?.length}`}</div>
</div>
<Body size="S">
<span class="tour-body">
{#if tourStep.layout}
<svelte:component this={tourStep.layout} />
{:else}
{tourStep?.body || ""}
{/if}
</span>
</Body>
<div class="tour-footer">
<div class="tour-navigation">
{#if tourStepIdx > 0}
<Button
secondary
on:click={previousStep}
disabled={tourStepIdx == 0}
>
<div>Back</div>
</Button>
{/if}
<Button cta on:click={nextStep}>
<div>{lastStep ? "Finish" : "Next"}</div>
</Button>
</div>
</div>
</Layout>
</div>
<Body size="S">
<span class="tour-body">
{#if tourStep.layout}
<svelte:component this={tourStep.layout} />
{:else}
{tourStep?.body || ""}
{/if}
</span>
</Body>
<div class="tour-footer">
<div class="tour-navigation">
{#if tourStepIdx > 0}
<Button
secondary
on:click={previousStep}
disabled={tourStepIdx == 0}
>
<div>Back</div>
</Button>
{/if}
<Button cta on:click={nextStep}>
<div>{lastStep ? "Finish" : "Next"}</div>
</Button>
</div>
</div>
</Layout>
</Popover>
{/key}
</Popover>
{/key}
{/if}
<style>
.tour-content {
padding: var(--spacing-xl);
}
.tour-navigation {
grid-gap: var(--spectrum-alias-grid-baseline);
display: flex;
justify-content: end;
}
:global([data-cy="tour-popover-menu"]) {
padding: 10px;
margin-top: var(--spacing-l);
}
.tour-body :global(.feature-list) {
margin-bottom: 0px;
padding-left: var(--spacing-xl);

View file

@ -1,5 +1,5 @@
<div>
In this section you can mange the data for your app:
In this section you can manage the data for your app:
<ul class="feature-list">
<li>Connect data sources</li>
<li>Edit data</li>

Some files were not shown because too many files have changed in this diff Show more