1
0
Fork 0
mirror of synced 2024-08-09 23:28:01 +12:00

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

View file

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

View file

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

View file

@ -11,10 +11,28 @@ filterTests(['all'], () => {
cy.addColumn("dog", "name", "Text") cy.addColumn("dog", "name", "Text")
cy.addColumn("dog", "age", "Number") cy.addColumn("dog", "age", "Number")
cy.addColumn("dog", "breed", "Options") cy.addColumn("dog", "breed", "Options")
// Works but the image doesn't resolve.
// cy.addColumn("dog", "image", "Attachment")
// cy.addRowAttachment(["fido", 12])
cy.navigateToFrontend() cy.navigateToFrontend()
cy.wait(1000) //allow the iframe some wiggle room 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", () => { it("should add a container", () => {
cy.addComponent("Layout", "Container").then(componentId => { cy.addComponent("Layout", "Container").then(componentId => {
cy.getComponent(componentId).should("exist") cy.getComponent(componentId).should("exist")
@ -65,6 +83,7 @@ filterTests(['all'], () => {
cy.contains("name").should("exist") cy.contains("name").should("exist")
cy.contains("age").should("exist") cy.contains("age").should("exist")
cy.contains("breed").should("exist") cy.contains("breed").should("exist")
// cy.contains("image").should("exist")
}) })
cy.getComponent(fieldGroupId) cy.getComponent(fieldGroupId)
.find("input") .find("input")
@ -97,7 +116,56 @@ filterTests(['all'], () => {
}) })
it("should set focus to the field setting when fields are added to a form", () => { 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]") cy.get("[data-cy=setting-dataSource]")
.contains("Custom") .contains("Custom")
.click() .click()
@ -105,43 +173,232 @@ filterTests(['all'], () => {
.contains("dog") .contains("dog")
.click() .click()
const componentTypeLabels = ["Text Field", "Number Field", "Password Field", const fieldTypeToColumnName = {
"Options Picker", "Checkbox", "Long Form Field", "Date Picker", "Attachment", "Text Field" : "name",
"JSON Field", "Multi-select Picker", "Relationship Picker"] "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) => { const refocusTest = (componentId) => {
let inputClasses
cy.getComponent(componentId) cy.getComponent(componentId)
.find(".showMe").should("exist").click({ force: true }) .find(".showMe").should("exist").click({ force: true })
cy.get("[data-cy=setting-field] .spectrum-InputGroup") cy.get("[data-cy=dataProvider-prop-control] .spectrum-Picker")
.should("have.class", "is-focused").within(() => { .should("have.class", "is-focused")
cy.get("input").should(($input) => {
inputClasses = Cypress.$($input).attr('class')
})
})
} }
const testFieldFocusOnCreate = (componentLabel) => { const testFocusOnCreate = (chartLabel) => {
let inputClasses cy.log("Adding: " + chartLabel)
cy.log("Adding: " + componentLabel) cy.addComponent("Chart", chartLabel).then((componentId) => {
cy.addComponent("Form", componentLabel).then((componentId) => {
refocusTest(componentId) refocusTest(componentId)
cy.get("[data-cy=setting-field] .spectrum-InputGroup") cy.get("[data-cy=dataProvider-prop-control] .spectrum-Picker")
.should("have.class", "is-focused").within(() => { .should("have.class", "is-focused")
cy.get("input").should(($input) => {
inputClasses = Cypress.$($input).attr('class')
})
})
}) })
} }
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 => { Cypress.Commands.add("createUser", email => {
// quick hacky recorded way to create a user // quick hacky recorded way to create a user
cy.contains("Users").click() cy.contains("Users").click()
@ -446,7 +464,7 @@ Cypress.Commands.add("addComponent", (category, component) => {
if (component) { if (component) {
cy.get(`[data-cy="component-${component}"]`).click({ force: true }) cy.get(`[data-cy="component-${component}"]`).click({ force: true })
} }
cy.wait(2000) cy.wait(1000)
cy.location().then(loc => { cy.location().then(loc => {
const params = loc.pathname.split("/") const params = loc.pathname.split("/")
const componentId = params[params.length - 1] const componentId = params[params.length - 1]

View file

@ -20,6 +20,7 @@ import analytics, { Events } from "analytics"
import { import {
findComponentType, findComponentType,
findComponentParent, findComponentParent,
findComponentPath,
findClosestMatchingComponent, findClosestMatchingComponent,
findAllMatchingComponents, findAllMatchingComponents,
findComponent, findComponent,
@ -423,6 +424,20 @@ export const getFrontendStore = () => {
state.currentView = "component" state.currentView = "component"
state.selectedComponentId = componentInstance._id 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 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 return store
} }

View file

@ -6,7 +6,6 @@
import ResetFieldsButton from "./PropertyControls/ResetFieldsButton.svelte" import ResetFieldsButton from "./PropertyControls/ResetFieldsButton.svelte"
import { getComponentForSettingType } from "./PropertyControls/componentSettings" import { getComponentForSettingType } from "./PropertyControls/componentSettings"
import { Utils } from "@budibase/frontend-core" import { Utils } from "@budibase/frontend-core"
import { onMount } from "svelte"
export let componentDefinition export let componentDefinition
export let componentInstance export let componentInstance
@ -83,37 +82,6 @@
setting.required === true && $store.builderFocus[0].key === setting.key 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> </script>
{#each sections as section, idx (section.name)} {#each sections as section, idx (section.name)}

View file

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

View file

@ -122,7 +122,12 @@
{displayValue} {displayValue}
</ActionButton> </ActionButton>
</div> </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="container">
<div class="search-area"> <div class="search-area">
<div class="alphabet-area"> <div class="alphabet-area">

View file

@ -4,6 +4,8 @@
readableToRuntimeBinding, readableToRuntimeBinding,
runtimeToReadableBinding, runtimeToReadableBinding,
} from "builderStore/dataBinding" } from "builderStore/dataBinding"
import { setContext } from "svelte"
import { store } from "builderStore"
export let label = "" export let label = ""
export let componentInstance = {} export let componentInstance = {}
@ -61,6 +63,28 @@
? defaultValue ? defaultValue
: enriched : 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> </script>
<div class="property-control" data-cy={`setting-${key}`}> <div class="property-control" data-cy={`setting-${key}`}>

View file

@ -20,7 +20,6 @@
import DevicePreviewSelect from "components/design/AppPreview/DevicePreviewSelect.svelte" import DevicePreviewSelect from "components/design/AppPreview/DevicePreviewSelect.svelte"
import Logo from "assets/bb-space-man.svg" import Logo from "assets/bb-space-man.svg"
import ScreenWizard from "components/design/NavigationPanel/ScreenWizard.svelte" 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 // Cache previous values so we don't update the URL more than necessary
let previousType let previousType
@ -197,27 +196,7 @@
</div> </div>
{#if $selectedComponent != null} {#if $selectedComponent != null}
<div <div class="components-pane">
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
})
}
}
}}
>
<PropertiesPanel /> <PropertiesPanel />
</div> </div>
{/if} {/if}

View file

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