Component Cypress tests and fixes

This commit is contained in:
Dean 2022-05-31 22:57:33 +01:00
parent f1111fffca
commit 692039cd34
12 changed files with 401 additions and 91 deletions

View File

@ -1,7 +1,8 @@
<script>
import "@spectrum-css/actionbutton/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
import { createEventDispatcher, getContext } from "svelte"
const dispatch = createEventDispatcher()
const context = getContext("builderFocus")
export let quiet = false
export let emphasized = false
@ -16,7 +17,11 @@
export let autofocus = false
let focus = false
$: focus = autofocus
let actionButton
$: focus = autofocus && actionButton !== undefined
$: if (focus) {
actionButton.focus()
}
function longPress(element) {
if (!longPressable) return
@ -41,6 +46,7 @@
<button
data-cy={dataCy}
bind:this={actionButton}
use:longPress
class:spectrum-ActionButton--quiet={quiet}
class:spectrum-ActionButton--emphasized={emphasized}
@ -57,6 +63,7 @@
}}
on:blur={() => {
focus = false
if (context) context.clear()
}}
>
{#if longPressable}

View File

@ -3,7 +3,7 @@
import "@spectrum-css/popover/dist/index-vars.css"
import "@spectrum-css/menu/dist/index-vars.css"
import { fly } from "svelte/transition"
import { createEventDispatcher } from "svelte"
import { createEventDispatcher, getContext } from "svelte"
export let value = null
export let id = null
@ -16,12 +16,16 @@
export let getOptionLabel = option => option
export let getOptionValue = option => option
const context = getContext("builderFocus")
const dispatch = createEventDispatcher()
let open = false
let focus = false
let comboInput
$: focus = autofocus && comboInput
$: focus = autofocus && comboInput !== undefined
$: if (focus) {
comboInput.focus()
}
const selectOption = value => {
dispatch("change", value)
@ -61,6 +65,7 @@
}}
on:blur={() => {
focus = false
context.clear()
}}
on:change={onType}
value={value || ""}

View File

@ -3,7 +3,7 @@
import "@spectrum-css/popover/dist/index-vars.css"
import "@spectrum-css/menu/dist/index-vars.css"
import { fly } from "svelte/transition"
import { createEventDispatcher } from "svelte"
import { createEventDispatcher, getContext } from "svelte"
import clickOutside from "../../Actions/click_outside"
import Search from "./Search.svelte"
@ -28,12 +28,16 @@
export let sort = false
export let autofocus = false
const context = getContext("builderFocus")
const dispatch = createEventDispatcher()
let searchTerm = null
let focus = false
let pickerButton
$: focus = autofocus && pickerButton !== null
$: focus = autofocus && pickerButton !== undefined
$: if (focus) {
pickerButton.focus()
}
$: sortedOptions = getSortedOptions(options, getOptionLabel, sort)
$: filteredOptions = getFilteredOptions(
@ -101,6 +105,7 @@
}}
on:blur={() => {
focus = false
if (context) context.clear()
}}
>
{#if fieldIcon}

View File

@ -11,10 +11,28 @@ filterTests(['all'], () => {
cy.addColumn("dog", "name", "Text")
cy.addColumn("dog", "age", "Number")
cy.addColumn("dog", "breed", "Options")
// Works but the image doesn't resolve.
// cy.addColumn("dog", "image", "Attachment")
// cy.addRowAttachment(["fido", 12])
cy.navigateToFrontend()
cy.wait(1000) //allow the iframe some wiggle room
})
//Use the tree to delete a selected component
const deleteSelectedComponent = () => {
cy.get(".nav-items-container .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.addComponent("Layout", "Container").then(componentId => {
cy.getComponent(componentId).should("exist")
@ -65,6 +83,7 @@ filterTests(['all'], () => {
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")
@ -97,7 +116,56 @@ filterTests(['all'], () => {
})
it("should set focus to the field setting when fields are added to a form", () => {
cy.addComponent("Form", "Form").then(() => {
cy.addComponent("Form", "Form").then((formId) => {
//For deletion
cy.get("[data-cy=setting-_instanceName] input")
.clear()
.type(formId)
.blur()
const componentTypeLabels = ["Text Field", "Number Field", "Password Field",
"Options Picker", "Checkbox", "Long Form Field", "Date Picker", "Attachment",
"JSON Field", "Multi-select Picker", "Relationship Picker"]
const refocusTest = (componentId) => {
cy.getComponent(componentId)
.find(".showMe").should("exist").click({ force: true })
cy.get("[data-cy=setting-field] .spectrum-InputGroup")
.should("have.class", "is-focused")
}
const testFieldFocusOnCreate = (componentLabel) => {
cy.log("Adding: " + componentLabel)
return cy.addComponent("Form", componentLabel).then((componentId) => {
refocusTest(componentId)
cy.get("[data-cy=setting-field] .spectrum-InputGroup")
.should("have.class", "is-focused")
})
}
cy.wait(1000)
cy.wrap(componentTypeLabels).each((label) => {
return testFieldFocusOnCreate(label)
}).then(()=>{
cy.get(".nav-items-container .nav-item").contains(formId).click({ force: true })
deleteSelectedComponent()
})
})
})
it("should clear the iframe place holder when a form field has been set", () => {
cy.addComponent("Form", "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()
@ -105,43 +173,232 @@ filterTests(['all'], () => {
.contains("dog")
.click()
const componentTypeLabels = ["Text Field", "Number Field", "Password Field",
"Options Picker", "Checkbox", "Long Form Field", "Date Picker", "Attachment",
"JSON Field", "Multi-select Picker", "Relationship Picker"]
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.addComponent("Form", componentLabel).then((componentId) => {
cy.getComponent(componentId)
.find(".placeholder_wrap").should("exist")
cy.get("[data-cy=setting-field] .spectrum-InputGroup")
.should("have.class", "is-focused")
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(".placeholder_wrap").should("not.exist")
})
}
cy.wait(500)
cy.wrap(componentTypeLabels).each((label) => {
return testFieldFocusOnCreate(label)
}).then(()=>{
cy.get(".nav-items-container .nav-item").contains(formId).click({ force: true })
deleteSelectedComponent()
})
})
})
it("should focus a charts settings on data provider if not nested in provider ", () => {
cy.addComponent("Layout", "Container").then((containerId) => {
//For deletion
cy.get("[data-cy=setting-_instanceName] input")
.clear()
.type(containerId)
.blur()
const chartTypeLabels = ["Bar Chart", "Line Chart", "Area Chart", "Pie Chart",
"Donut Chart", "Candlestick Chart"]
const refocusTest = (componentId) => {
let inputClasses
cy.getComponent(componentId)
.find(".showMe").should("exist").click({ force: true })
cy.get("[data-cy=setting-field] .spectrum-InputGroup")
.should("have.class", "is-focused").within(() => {
cy.get("input").should(($input) => {
inputClasses = Cypress.$($input).attr('class')
})
})
cy.get("[data-cy=dataProvider-prop-control] .spectrum-Picker")
.should("have.class", "is-focused")
}
const testFieldFocusOnCreate = (componentLabel) => {
let inputClasses
cy.log("Adding: " + componentLabel)
cy.addComponent("Form", componentLabel).then((componentId) => {
const testFocusOnCreate = (chartLabel) => {
cy.log("Adding: " + chartLabel)
cy.addComponent("Chart", chartLabel).then((componentId) => {
refocusTest(componentId)
cy.get("[data-cy=setting-field] .spectrum-InputGroup")
.should("have.class", "is-focused").within(() => {
cy.get("input").should(($input) => {
inputClasses = Cypress.$($input).attr('class')
})
})
cy.get("[data-cy=dataProvider-prop-control] .spectrum-Picker")
.should("have.class", "is-focused")
})
}
componentTypeLabels.forEach( testFieldFocusOnCreate )
cy.wait(1000)
cy.wrap(chartTypeLabels).each((label) => {
return testFocusOnCreate(label)
})
.then(()=>{
cy.get(".nav-items-container .nav-item").contains(containerId).click({ force: true })
deleteSelectedComponent()
})
})
})
it("should populate the provider for charts with a data provider in its path", () => {
cy.addComponent("Data", "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.addComponent("Chart", 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-items-container .nav-item").contains(providerId).click({ force: true })
deleteSelectedComponent()
})
})
})
it("should replace the placeholder when a url is set on an image", () => {
cy.addComponent("Elements", "Image").then((imageId) => {
cy.get("[data-cy=url-prop-control] .spectrum-InputGroup")
.should("have.class", "is-focused")
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(".placeholder_wrap").should("not.exist")
cy.getComponent(imageId)
.find(`img[alt=${imageId}]`).should("exist")
cy.get(".nav-items-container .nav-item")
.contains(imageId)
.click({ force: true })
deleteSelectedComponent()
})
})
it("should add a markdown component.", () => {
cy.addComponent("Elements", "Markdown Viewer").then((markdownId) => {
cy.get("[data-cy=value-prop-control] .spectrum-InputGroup")
.should("have.class", "is-focused")
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(".placeholder_wrap").should("not.exist")
cy.getComponent(markdownId)
.find(".editor-preview-full h1").contains("Hi")
cy.get(".nav-items-container .nav-item")
.contains(markdownId)
.click({ force: true })
deleteSelectedComponent()
})
})
it("should direct the user when adding an Icon component.", () => {
cy.addComponent("Elements", "Icon").then((iconId) => {
cy.get("[data-cy=icon-prop-control] .spectrum-ActionButton")
.should("have.class", "is-focused")
cy.getComponent(iconId)
.find(".placeholder_wrap").should("exist")
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(".placeholder_wrap").should("not.exist")
cy.getComponent(iconId)
.find("i.ri-save-fill").should("exist")
cy.get(".nav-items-container .nav-item")
.contains(iconId)
.click({ force: true })
deleteSelectedComponent()
})
})
})
})

View File

@ -423,6 +423,24 @@ Cypress.Commands.add("addRowMultiValue", values => {
})
})
Cypress.Commands.add("addRowAttachment", (values, name, path) => {
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-Dropzone").selectFile(path, {
action: "drag-drop",
})
cy.get(".gallery .filename").contains(name)
cy.get(".confirm-wrap .spectrum-Button")
.contains("Create Row")
.click({ force: true })
})
})
Cypress.Commands.add("createUser", email => {
// quick hacky recorded way to create a user
cy.contains("Users").click()
@ -446,7 +464,7 @@ Cypress.Commands.add("addComponent", (category, component) => {
if (component) {
cy.get(`[data-cy="component-${component}"]`).click({ force: true })
}
cy.wait(2000)
cy.wait(1000)
cy.location().then(loc => {
const params = loc.pathname.split("/")
const componentId = params[params.length - 1]

View File

@ -20,6 +20,7 @@ import analytics, { Events } from "analytics"
import {
findComponentType,
findComponentParent,
findComponentPath,
findClosestMatchingComponent,
findAllMatchingComponents,
findComponent,
@ -423,6 +424,20 @@ export const getFrontendStore = () => {
state.currentView = "component"
state.selectedComponentId = componentInstance._id
const focusSetting = resolveComponentFocus(
asset?.props,
componentInstance
)
if (focusSetting) {
state.builderFocus = [
{
key: focusSetting.key,
target: state.selectedComponentId,
location: "component_settings",
},
]
}
return state
})
@ -665,5 +680,32 @@ export const getFrontendStore = () => {
},
}
// Determine the initial focus for newly created components
// Take into account the fact that data providers should be
// skipped if they will be inherited from the path
const resolveComponentFocus = (asset_props, componentInstance) => {
const definition = store.actions.components.getDefinition(
componentInstance._component
)
let providerIdx = -1
let required = definition.settings.filter((s, idx) => {
if (s.type === "dataProvider") {
providerIdx = idx
}
return s.required
})
if (providerIdx > -1) {
const path = findComponentPath(asset_props, componentInstance._id)
const providers = path.filter(c =>
c._component?.endsWith("/dataprovider")
)
if (providers.length) {
required = required.splice(providerIdx, 1)
}
}
return required[0]
}
return store
}

View File

@ -6,7 +6,6 @@
import ResetFieldsButton from "./PropertyControls/ResetFieldsButton.svelte"
import { getComponentForSettingType } from "./PropertyControls/componentSettings"
import { Utils } from "@budibase/frontend-core"
import { onMount } from "svelte"
export let componentDefinition
export let componentInstance
@ -83,37 +82,6 @@
setting.required === true && $store.builderFocus[0].key === setting.key
)
}
onMount(() => {
const emptyFields = (definition, options) => {
if (!options) {
return []
}
return definition?.settings
? definition.settings.filter(setting => {
return (
setting.required &&
(!options[setting.key] || options[setting.key] == "")
)
})
: []
}
let target = emptyFields(componentDefinition, componentInstance)[0]
if (target) {
store.update(state => ({
...state,
builderFocus: [
{
location: "component_settings",
key: target.key,
target: componentInstance._id,
},
],
}))
}
})
</script>
{#each sections as section, idx (section.name)}

View File

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

View File

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

View File

@ -4,6 +4,8 @@
readableToRuntimeBinding,
runtimeToReadableBinding,
} from "builderStore/dataBinding"
import { setContext } from "svelte"
import { store } from "builderStore"
export let label = ""
export let componentInstance = {}
@ -61,6 +63,28 @@
? defaultValue
: enriched
}
setContext("builderFocus", {
clear: () => {
if (!$store?.builderFocus) {
return
}
store.update(state => {
const updatedFocus = $store?.builderFocus?.filter(focus => {
return (
focus.location === "component_settings" &&
focus.target !== componentInstance._id
)
})
if (updatedFocus?.length > 0) {
state.builderFocus = updatedFocus
} else {
delete state.builderFocus
}
return state
})
},
})
</script>
<div class="property-control" data-cy={`setting-${key}`}>

View File

@ -20,7 +20,6 @@
import DevicePreviewSelect from "components/design/AppPreview/DevicePreviewSelect.svelte"
import Logo from "assets/bb-space-man.svg"
import ScreenWizard from "components/design/NavigationPanel/ScreenWizard.svelte"
import { clickOutside } from "@budibase/bbui"
// Cache previous values so we don't update the URL more than necessary
let previousType
@ -197,27 +196,7 @@
</div>
{#if $selectedComponent != null}
<div
class="components-pane"
use:clickOutside={() => {
if ($store?.builderFocus) {
const otherSettings = $store?.builderFocus?.filter(field => {
return field.location !== "component_settings"
})
if (otherSettings.length) {
store.update(state => {
state.builderFocus = otherSettings
return state
})
} else {
store.update(state => {
delete state.builderFocus
return state
})
}
}
}}
>
<div class="components-pane">
<PropertiesPanel />
</div>
{/if}

View File

@ -19,7 +19,6 @@
return componentKey
}
//Corify this somewhere
const emptyFields = (definition, options) => {
if (!options) {
return []
@ -62,9 +61,6 @@
</div>
{:else}
{text || $component.name || "Placeholder"}
<!-- {#if definition.hasChildren}
<span>: Add a component or two!</span>
{/if} -->
{/if}
</div>
{/if}