mirror of https://github.com/Budibase/budibase.git
43 changed files with 1168 additions and 1023 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,8 @@ |
|||
{ |
|||
"javascript.format.enable": false, |
|||
"svelte.plugin.svelte.format.enable": false, |
|||
"html.format.enable": false, |
|||
"json.format.enable": false, |
|||
"editor.trimAutoWhitespace": false, |
|||
"sass.format.deleteWhitespace": false |
|||
} |
|||
|
After Width: | Height: | Size: 400 B |
|
After Width: | Height: | Size: 339 B |
|
After Width: | Height: | Size: 280 B |
|
After Width: | Height: | Size: 415 B |
|
After Width: | Height: | Size: 346 B |
@ -0,0 +1,5 @@ |
|||
export { default as LayoutIcon } from './Layout.svelte'; |
|||
export { default as PaintIcon } from './Paint.svelte'; |
|||
export { default as TerminalIcon } from './Terminal.svelte'; |
|||
export { default as InputIcon } from './Input.svelte'; |
|||
export { default as ImageIcon } from './Image.svelte'; |
|||
@ -0,0 +1,54 @@ |
|||
<script> |
|||
export let meta = []; |
|||
export let size = ''; |
|||
export let values = []; |
|||
</script> |
|||
|
|||
<div class="inputs {size}"> |
|||
{#each meta as { placeholder }, i} |
|||
<input type="number" placeholder="{placeholder}" bind:value={values[i]}/> |
|||
{/each} |
|||
</div> |
|||
|
|||
<style> |
|||
.inputs { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
|
|||
} |
|||
|
|||
input { |
|||
width: 83px; |
|||
font-size: 12px; |
|||
font-weight: 700; |
|||
color: #163057; |
|||
opacity: 0.7; |
|||
padding: 5px 10px; |
|||
box-sizing: border-box; |
|||
border: 1px solid #DBDBDB; |
|||
border-radius: 2px; |
|||
outline: none; |
|||
} |
|||
|
|||
input[type=number]::-webkit-inner-spin-button, |
|||
input[type=number]::-webkit-outer-spin-button { |
|||
-webkit-appearance: none; |
|||
-moz-appearance: none; |
|||
appearance: none; |
|||
margin: 0; |
|||
} |
|||
|
|||
.small > input { |
|||
width: 38px; |
|||
height: 38px; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
text-align: center; |
|||
padding: 0; |
|||
} |
|||
|
|||
.small > input::placeholder { |
|||
text-align: center; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,119 @@ |
|||
<script> |
|||
let snippets = []; |
|||
let current_snippet = 0; |
|||
let snippet_text = '' |
|||
let id = 0; |
|||
|
|||
function save_snippet() { |
|||
if (!snippet_text) return; |
|||
|
|||
const index = snippets.findIndex(({ id }) => current_snippet === id); |
|||
|
|||
if (index > -1) { |
|||
snippets[index].snippet = snippet_text; |
|||
} else { |
|||
snippets = snippets.concat({ snippet: snippet_text , id: id }); |
|||
} |
|||
snippet_text = ''; |
|||
current_snippet = ++id; |
|||
} |
|||
|
|||
function edit_snippet(id) { |
|||
const { snippet, id: _id } = snippets.find(({ id:_id }) => _id === id); |
|||
current_snippet = id |
|||
snippet_text = snippet; |
|||
} |
|||
</script> |
|||
|
|||
<h3>Code</h3> |
|||
|
|||
<p>Use the code box below to add snippets of javascript to enhance your webapp</p> |
|||
|
|||
<div class="editor"> |
|||
<textarea class="code" bind:value={snippet_text} /> |
|||
<button on:click={save_snippet}>Save</button> |
|||
</div> |
|||
|
|||
<div class="snippets"> |
|||
<h3>Snippets added</h3> |
|||
{#each snippets as { id, snippet } } |
|||
<div class="snippet"> |
|||
<pre class="code">{snippet}</pre> |
|||
<button on:click={() => edit_snippet(id)}>Edit</button> |
|||
</div> |
|||
{/each} |
|||
</div> |
|||
|
|||
<style> |
|||
h3 { |
|||
text-transform: uppercase; |
|||
font-size: 12px; |
|||
font-weight: 700; |
|||
color: #8997ab; |
|||
margin-bottom: 10px; |
|||
} |
|||
|
|||
p { |
|||
font-size: 12px; |
|||
color: #333; |
|||
margin-top: 0; |
|||
} |
|||
|
|||
.editor { |
|||
position: relative; |
|||
} |
|||
|
|||
.code { |
|||
width: 100%; |
|||
outline: none; |
|||
border: none; |
|||
background: #173157; |
|||
border-radius: 5px; |
|||
box-sizing: border-box; |
|||
white-space: pre; |
|||
color: #eee; |
|||
padding: 10px; |
|||
font-family: monospace; |
|||
overflow-y: scroll; |
|||
} |
|||
|
|||
.editor textarea { |
|||
resize: none; |
|||
height: 150px; |
|||
} |
|||
|
|||
button { |
|||
position: absolute; |
|||
box-shadow: 0 0 black; |
|||
color: #eee; |
|||
right: 5px; |
|||
bottom: 10px; |
|||
background: none; |
|||
border: none; |
|||
text-transform: uppercase; |
|||
font-size: 9px; |
|||
font-weight: 600; |
|||
outline: none; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.snippets { |
|||
margin-top: 20px; |
|||
} |
|||
|
|||
.snippet { |
|||
position: relative; |
|||
margin-top: 5px; |
|||
} |
|||
|
|||
.snippet pre { |
|||
background: #f9f9f9; |
|||
color: #333; |
|||
max-height: 150px; |
|||
} |
|||
|
|||
.snippet button { |
|||
color: #ccc; |
|||
} |
|||
|
|||
</style> |
|||
@ -0,0 +1,150 @@ |
|||
<script> |
|||
|
|||
import PropsView from "./PropsView.svelte"; |
|||
import { store } from "../builderStore"; |
|||
import { isRootComponent } from "./pagesParsing/searchComponents"; |
|||
import IconButton from "../common/IconButton.svelte"; |
|||
import Textbox from "../common/Textbox.svelte"; |
|||
import { pipe } from "../common/core"; |
|||
import { |
|||
getScreenInfo |
|||
} from "./pagesParsing/createProps"; |
|||
import { LayoutIcon, PaintIcon, TerminalIcon } from '../common/Icons/'; |
|||
import CodeEditor from './CodeEditor.svelte'; |
|||
import LayoutEditor from './LayoutEditor.svelte'; |
|||
|
|||
import { |
|||
cloneDeep, |
|||
join, |
|||
split, |
|||
map, |
|||
keys, |
|||
isUndefined, |
|||
last |
|||
} from "lodash/fp"; |
|||
import { assign } from "lodash"; |
|||
|
|||
let component; |
|||
let name = ""; |
|||
let description = ""; |
|||
let tagsString = ""; |
|||
let nameInvalid = ""; |
|||
let componentInfo = {}; |
|||
let modalElement |
|||
let propsValidationErrors = []; |
|||
let originalName=""; |
|||
let components; |
|||
let ignoreStore = false; |
|||
|
|||
// $: shortName = last(name.split("/")); |
|||
|
|||
store.subscribe(s => { |
|||
if(ignoreStore) return; |
|||
component = s.currentComponentInfo; |
|||
if(!component) return; |
|||
originalName = component.name; |
|||
name = component.name; |
|||
description = component.description; |
|||
tagsString = join(", ")(component.tags); |
|||
componentInfo = s.currentComponentInfo; |
|||
components = s.components; |
|||
}); |
|||
|
|||
const onPropsChanged = store.updateComponentProp; |
|||
|
|||
let current_view = 'props'; |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
<ul> |
|||
<li> |
|||
<button class:selected={current_view === 'props'} on:click={() => current_view = 'props'}> |
|||
<PaintIcon /> |
|||
</button> |
|||
</li> |
|||
<li> |
|||
<button class:selected={current_view === 'layout'} on:click={() => current_view = 'layout'}> |
|||
<LayoutIcon /> |
|||
</button> |
|||
</li> |
|||
<li> |
|||
<button class:selected={current_view === 'code'} on:click={() => current_view = 'code'}> |
|||
<TerminalIcon /> |
|||
</button> |
|||
</li> |
|||
</ul> |
|||
|
|||
{#if !componentInfo.component} |
|||
<div class="component-props-container"> |
|||
|
|||
{#if current_view === 'props'} |
|||
<PropsView {componentInfo} {components} {onPropsChanged} /> |
|||
{:else if current_view === 'layout'} |
|||
<LayoutEditor /> |
|||
{:else} |
|||
<CodeEditor /> |
|||
{/if} |
|||
|
|||
</div> |
|||
{:else} |
|||
<h1> This is a screen, this will be dealt with later</h1> |
|||
{/if} |
|||
|
|||
</div> |
|||
|
|||
|
|||
<style> |
|||
|
|||
.root { |
|||
height: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
|
|||
} |
|||
|
|||
.title > div:nth-child(1) { |
|||
grid-column-start: name; |
|||
color: var(--secondary100); |
|||
} |
|||
|
|||
.title > div:nth-child(2) { |
|||
grid-column-start: actions; |
|||
} |
|||
|
|||
.component-props-container { |
|||
margin-top: 10px; |
|||
flex: 1 1 auto; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
ul { |
|||
list-style: none; |
|||
display: flex; |
|||
padding: 0; |
|||
} |
|||
|
|||
li { |
|||
margin-right: 20px; |
|||
background: none; |
|||
border-radius: 5px; |
|||
width: 48px; |
|||
height: 48px; |
|||
} |
|||
|
|||
li button { |
|||
width: 100%; |
|||
height: 100%; |
|||
background: none; |
|||
border: none; |
|||
border-radius: 5px; |
|||
padding: 12px; |
|||
outline: none; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.selected { |
|||
color: var(--button-text); |
|||
background: var(--background-button)!important; |
|||
} |
|||
|
|||
</style> |
|||
@ -0,0 +1,24 @@ |
|||
<script> |
|||
import { last } from "lodash/fp"; |
|||
import { pipe } from "../common/core"; |
|||
|
|||
export let components = []; |
|||
export let onSelect = () => {}; |
|||
|
|||
const capitalise = s => s.substring(0,1).toUpperCase() + s.substring(1); |
|||
const get_name = s => last(s.split('/')); |
|||
const get_capitalised_name = name => pipe(name, [get_name,capitalise]); |
|||
</script> |
|||
|
|||
{#each components as component} |
|||
<ul> |
|||
<li on:click|stopPropagation={() => onSelect(component)}> |
|||
{get_capitalised_name(component._component)} |
|||
|
|||
{#if component._children} |
|||
<svelte:self components={component._children}/> |
|||
{/if} |
|||
</li> |
|||
|
|||
</ul> |
|||
{/each} |
|||
@ -0,0 +1,116 @@ |
|||
<script> |
|||
import InputGroup from '../common/Inputs/InputGroup.svelte'; |
|||
|
|||
let grid_values = ['', '', '', '']; |
|||
let column_values = ['', '']; |
|||
let row_values = ['', '']; |
|||
let gap_values = ['']; |
|||
let margin_values = ['', '', '', '']; |
|||
let padding_values = ['', '', '', '']; |
|||
let zindex_values = ['']; |
|||
|
|||
const tbrl = [ |
|||
{ placeholder: 'T' }, |
|||
{ placeholder: 'R' }, |
|||
{ placeholder: 'B' }, |
|||
{ placeholder: 'L' } |
|||
]; |
|||
|
|||
const se = [ |
|||
{ placeholder: 'START' }, |
|||
{ placeholder: 'END' }, |
|||
] |
|||
|
|||
const single = [{ placeholder: '' }]; |
|||
</script> |
|||
|
|||
|
|||
<h3>Layout</h3> |
|||
|
|||
<h4>Positioning</h4> |
|||
|
|||
<div class="layout-pos"> |
|||
<div class="grid"> |
|||
<h5>Grid Area:</h5> |
|||
<InputGroup meta={tbrl} bind:values={grid_values} size="small"/> |
|||
</div> |
|||
|
|||
<div class="grid"> |
|||
<h5>Column:</h5> |
|||
<InputGroup meta={se} bind:values={column_values} /> |
|||
</div> |
|||
|
|||
<div class="grid"> |
|||
<h5>Row:</h5> |
|||
<InputGroup meta={se} bind:values={row_values} /> |
|||
</div> |
|||
|
|||
<div class="grid"> |
|||
<h5>Gap:</h5> |
|||
<InputGroup meta={single} bind:values={gap_values} /> |
|||
</div> |
|||
</div> |
|||
|
|||
<h4>Spacing</h4> |
|||
|
|||
<div class="layout-spacing"> |
|||
<div class="grid"> |
|||
<h5>Margin:</h5> |
|||
<InputGroup meta={tbrl} bind:values={margin_values} size="small"/> |
|||
</div> |
|||
|
|||
<div class="grid"> |
|||
<h5>Padding:</h5> |
|||
<InputGroup meta={tbrl} bind:values={padding_values} size="small"/> |
|||
</div> |
|||
</div> |
|||
|
|||
<h4>Z-Index</h4> |
|||
|
|||
<div class="layout-layer"> |
|||
<div class="grid"> |
|||
<h5>Z-Index:</h5> |
|||
<InputGroup meta={single} bind:values={zindex_values}/> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
h3 { |
|||
text-transform: uppercase; |
|||
font-size: 12px; |
|||
font-weight: 700; |
|||
color: #8997ab; |
|||
margin-bottom: 10px; |
|||
} |
|||
|
|||
h4 { |
|||
text-transform: uppercase; |
|||
font-size: 10px; |
|||
font-weight: 700; |
|||
color: #163057; |
|||
opacity: 0.3; |
|||
margin-bottom: 15px; |
|||
} |
|||
|
|||
h5 { |
|||
font-size: 12px; |
|||
font-weight: 700; |
|||
color: #163057; |
|||
opacity: 0.6; |
|||
padding-top: 12px; |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
div > div { |
|||
display: grid; |
|||
grid-template-rows: 1fr; |
|||
grid-gap: 10px; |
|||
height: 40px; |
|||
margin-bottom: 15px; |
|||
} |
|||
|
|||
.grid { |
|||
grid-template-columns: 70px 1fr; |
|||
} |
|||
|
|||
</style> |
|||
@ -1,143 +0,0 @@ |
|||
import { types } from "./types"; |
|||
import { |
|||
createProps, arrayElementComponentName |
|||
} from "./createProps"; |
|||
import { isString } from "util"; |
|||
import { |
|||
includes, filter, map, keys, |
|||
flatten, flattenDeep, each, |
|||
indexOf, isUndefined |
|||
} from "lodash/fp"; |
|||
import { common } from "../../../../core/src"; |
|||
import { |
|||
isBinding |
|||
} from "../../common/binding"; |
|||
|
|||
const pipe = common.$; |
|||
|
|||
const makeError = (errors, propName, stack) => (message) => |
|||
errors.push({ |
|||
stack, |
|||
propName, |
|||
error:message}); |
|||
|
|||
export const recursivelyValidate = (rootProps, getComponent, stack=[]) => { |
|||
|
|||
if(!rootProps._component) { |
|||
const errs = []; |
|||
makeError(errs, "_component", stack)("Component is not set"); |
|||
return errs; |
|||
// this would break everything else anyway
|
|||
} |
|||
|
|||
const componentDef = getComponent( |
|||
rootProps._component); |
|||
|
|||
|
|||
const errors = validateProps( |
|||
componentDef, |
|||
rootProps, |
|||
stack, |
|||
true); |
|||
|
|||
const validateChildren = (_props, _stack) => |
|||
!_props._children |
|||
? [] |
|||
: pipe(_props._children, [ |
|||
map(child => recursivelyValidate( |
|||
child, |
|||
getComponent, |
|||
[..._stack, _props._children.indexOf(child)])) |
|||
]); |
|||
|
|||
const childErrors = validateChildren( |
|||
rootProps, stack); |
|||
|
|||
return flattenDeep([errors, ...childErrors]); |
|||
} |
|||
|
|||
const expandPropDef = propDef => |
|||
isString(propDef) |
|||
? types[propDef].defaultDefinition() |
|||
: propDef; |
|||
|
|||
|
|||
|
|||
export const validateProps = (componentDefinition, props, stack=[], isFinal=true) => { |
|||
|
|||
const errors = []; |
|||
|
|||
if(isFinal && !props._component) { |
|||
makeError(errors, "_component", stack)("Component is not set"); |
|||
return errors; |
|||
// this would break everything else anyway
|
|||
} |
|||
|
|||
const propsDefinition = componentDefinition.props; |
|||
|
|||
for(let propDefName in props) { |
|||
|
|||
if(propDefName === "_component") continue; |
|||
if(propDefName === "_children") continue; |
|||
if(propDefName === "_layout") continue; |
|||
|
|||
const propDef = expandPropDef(propsDefinition[propDefName]); |
|||
|
|||
const type = types[propDef.type]; |
|||
|
|||
const error = makeError(errors, propDefName, stack); |
|||
|
|||
const propValue = props[propDefName]; |
|||
|
|||
// component declarations dont need to define al props.
|
|||
if(!isFinal && isUndefined(propValue)) continue; |
|||
|
|||
if(isFinal && propDef.required && propValue) { |
|||
error(`Property ${propDefName} is required`); |
|||
continue; |
|||
} |
|||
|
|||
if(isBinding(propValue)) { |
|||
if(propDef.type === "event") { |
|||
error(`Cannot apply binding to type ${propDef.type}`); |
|||
continue; |
|||
} |
|||
} |
|||
else if(!type.isOfType(propValue)) { |
|||
error(`Property ${propDefName} is not of type ${propDef.type}. Actual value ${propValue}`) |
|||
continue; |
|||
} |
|||
|
|||
|
|||
if(propDef.type === "options" |
|||
&& propValue |
|||
&& !isBinding(propValue) |
|||
&& !includes(propValue)(propDef.options)) { |
|||
error(`Property ${propDefName} is not one of allowed options. Acutal value is ${propValue}`); |
|||
} |
|||
|
|||
} |
|||
|
|||
return errors; |
|||
} |
|||
|
|||
export const validateComponentDefinition = (componentDefinition) => { |
|||
const { errors } = createProps(componentDefinition); |
|||
|
|||
const propDefinitions = expandPropDef(componentDefinition.props); |
|||
|
|||
pipe(propDefinitions, [ |
|||
keys, |
|||
map(k => ({ |
|||
propDef:propDefinitions[k], |
|||
propName:k |
|||
})), |
|||
filter(d => d.propDef.type === "options" |
|||
&& (!d.propDef.options || d.propDef.options.length === 0)), |
|||
each(d => makeError(errors, d.propName)(`${d.propName} does not have any options`)) |
|||
]); |
|||
|
|||
return errors; |
|||
|
|||
} |
|||
|
|||
@ -1,232 +0,0 @@ |
|||
import { |
|||
validateComponentDefinition, |
|||
validateProps, |
|||
recursivelyValidate |
|||
} from "../src/userInterface/pagesParsing/validateProps"; |
|||
import { createProps } from "../src/userInterface/pagesParsing/createProps"; |
|||
import { |
|||
setBinding |
|||
} from "../src/common/binding"; |
|||
|
|||
// not that allot of this functionality is covered
|
|||
// in createDefaultProps - as validate props uses that.
|
|||
|
|||
describe("validateComponentDefinition", () => { |
|||
|
|||
|
|||
it("should return error when no options for options field", () => { |
|||
|
|||
const compDef = { |
|||
name:"some_component", |
|||
props: { |
|||
size: { |
|||
type: "options", |
|||
options: [] |
|||
} |
|||
} |
|||
}; |
|||
|
|||
const errors = validateComponentDefinition(compDef); |
|||
|
|||
expect(errors.length).toEqual(1); |
|||
expect(errors[0].propName).toBe("size"); |
|||
|
|||
}); |
|||
|
|||
it("should not return error when options field has options", () => { |
|||
|
|||
const compDef = { |
|||
name: "some_component", |
|||
props: { |
|||
size: { |
|||
type: "options", |
|||
options: ["small", "medium", "large"] |
|||
} |
|||
} |
|||
}; |
|||
|
|||
const errors = validateComponentDefinition(compDef); |
|||
|
|||
expect(errors).toEqual([]); |
|||
|
|||
}); |
|||
|
|||
}); |
|||
|
|||
const validComponentDef = { |
|||
name: "some_component", |
|||
props: { |
|||
size: { |
|||
type: "options", |
|||
options: ["small", "medium", "large"], |
|||
default:"medium" |
|||
}, |
|||
rowCount : "number" |
|||
} |
|||
}; |
|||
|
|||
const childComponentDef = { |
|||
name: "child_component", |
|||
props: { |
|||
width: "number", |
|||
units: { |
|||
type: "string", |
|||
default: "px" |
|||
} |
|||
} |
|||
}; |
|||
|
|||
const validProps = () => { |
|||
|
|||
const { props } = createProps(validComponentDef); |
|||
props._children.push( |
|||
createProps(childComponentDef)); |
|||
return props; |
|||
} |
|||
|
|||
describe("validateProps", () => { |
|||
|
|||
it("should have no errors with a big list of valid props", () => { |
|||
|
|||
const errors = validateProps(validComponentDef, validProps(), [], true); |
|||
expect(errors).toEqual([]); |
|||
|
|||
}); |
|||
|
|||
it("should return error with invalid value", () => { |
|||
|
|||
const props = validProps(); |
|||
props.rowCount = "1"; |
|||
const errors = validateProps(validComponentDef, props, [], true); |
|||
expect(errors.length).toEqual(1); |
|||
expect(errors[0].propName).toBe("rowCount"); |
|||
|
|||
}); |
|||
|
|||
it("should return error with invalid option", () => { |
|||
|
|||
const props = validProps(); |
|||
props.size = "really_small"; |
|||
const errors = validateProps(validComponentDef, props, [], true); |
|||
expect(errors.length).toEqual(1); |
|||
expect(errors[0].propName).toBe("size"); |
|||
|
|||
}); |
|||
|
|||
it("should not return error when has binding", () => { |
|||
const props = validProps(); |
|||
props._children[0].width = setBinding({path:"some_path"}); |
|||
props.size = setBinding({path:"other path", fallback:"small"}); |
|||
const errors = validateProps(validComponentDef, props, [], true); |
|||
expect(errors.length).toEqual(0); |
|||
}); |
|||
}); |
|||
|
|||
describe("recursivelyValidateProps", () => { |
|||
|
|||
const rootComponent = { |
|||
name: "rootComponent", |
|||
children: true, |
|||
props: { |
|||
width: "number" |
|||
} |
|||
}; |
|||
|
|||
const todoListComponent = { |
|||
name: "todoListComponent", |
|||
props:{ |
|||
showTitle: "bool" |
|||
} |
|||
}; |
|||
|
|||
const headerComponent = { |
|||
name: "headerComponent", |
|||
props: { |
|||
text: "string" |
|||
} |
|||
}; |
|||
|
|||
const iconComponent = { |
|||
name: "iconComponent", |
|||
props: { |
|||
iconName: "string" |
|||
} |
|||
}; |
|||
|
|||
const navItemComponent = { |
|||
name: "navItemComponent", |
|||
props: { |
|||
text: "string" |
|||
} |
|||
}; |
|||
|
|||
const getComponent = name => ({ |
|||
rootComponent, |
|||
todoListComponent, |
|||
headerComponent, |
|||
iconComponent, |
|||
navItemComponent |
|||
})[name]; |
|||
|
|||
const rootProps = () => ({ |
|||
_component: "rootComponent", |
|||
width: 100, |
|||
_children: [{ |
|||
_component: "todoListComponent", |
|||
showTitle: true, |
|||
_children : [ |
|||
{ |
|||
_component: "navItemComponent", |
|||
text: "todos" |
|||
}, |
|||
{ |
|||
_component: "headerComponent", |
|||
text: "Your todo list" |
|||
}, |
|||
{ |
|||
_component: "iconComponent", |
|||
iconName: "fa fa-list" |
|||
}, |
|||
{ |
|||
_component: "iconComponent", |
|||
iconName:"fa fa-cog" |
|||
} |
|||
] |
|||
}] |
|||
}); |
|||
|
|||
it("should return no errors for valid structure", () => { |
|||
const result = recursivelyValidate( |
|||
rootProps(), |
|||
getComponent); |
|||
|
|||
expect(result).toEqual([]); |
|||
}); |
|||
|
|||
it("should return error on root component", () => { |
|||
const root = rootProps(); |
|||
root.width = "yeeeoooo"; |
|||
const result = recursivelyValidate(root, getComponent); |
|||
expect(result.length).toBe(1); |
|||
expect(result[0].propName).toBe("width"); |
|||
}); |
|||
|
|||
it("should return error on first nested child component", () => { |
|||
const root = rootProps(); |
|||
root._children[0].showTitle = "yeeeoooo"; |
|||
const result = recursivelyValidate(root, getComponent); |
|||
expect(result.length).toBe(1); |
|||
expect(result[0].stack).toEqual([0]); |
|||
expect(result[0].propName).toBe("showTitle"); |
|||
}); |
|||
|
|||
it("should return error on second nested child component", () => { |
|||
const root = rootProps(); |
|||
root._children[0]._children[0].text = false; |
|||
const result = recursivelyValidate(root, getComponent); |
|||
expect(result.length).toBe(1); |
|||
expect(result[0].stack).toEqual([0,0]); |
|||
expect(result[0].propName).toBe("text"); |
|||
}); |
|||
|
|||
}); |
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,3 +1,8 @@ |
|||
myapps/ |
|||
config.js |
|||
<<<<<<< HEAD |
|||
/builder/* |
|||
!/builder/assets/ |
|||
======= |
|||
builder/ |
|||
>>>>>>> ee5a4e8c962b29242152cbbd8065d8f3ccf65eaf |
|||
|
|||
Binary file not shown.
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue