1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-03-17 21:18:37 +02:00

Feature/#250 wait style (#255)

* Added support for parsed styles

* Added stdlib function.

* Added e2e tests

* Added e2e tests for STYLE_* functions
This commit is contained in:
Tim Voronov 2019-03-13 14:51:30 -04:00 committed by GitHub
parent 391836cbb2
commit 376ad77404
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1225 additions and 217 deletions

9
Gopkg.lock generated
View File

@ -65,6 +65,14 @@
pruneopts = "UT"
revision = "d547d1d9531ed93dbdebcbff7f83e7c876a1e0ee"
[[projects]]
digest = "1:fc51ecee8f31d03436c1a0167eb1e383ad0a241d02272541853f3995374a08f1"
name = "github.com/gorilla/css"
packages = ["scanner"]
pruneopts = "UT"
revision = "398b0b046082ecb3694c01bec6b336a06a4e530a"
version = "v1.0.0"
[[projects]]
digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d"
name = "github.com/gorilla/websocket"
@ -297,6 +305,7 @@
"github.com/corpix/uarand",
"github.com/derekparker/trie",
"github.com/gofrs/uuid",
"github.com/gorilla/css/scanner",
"github.com/labstack/echo",
"github.com/mafredri/cdp",
"github.com/mafredri/cdp/devtool",

View File

@ -45,6 +45,10 @@
name = "github.com/PuerkitoBio/goquery"
version = "1.5.0"
[[constraint]]
name = "github.com/gorilla/css"
version = "v1.0.0"
[[constraint]]
name = "github.com/gofrs/uuid"
version = "3.1.2"

View File

@ -2,23 +2,52 @@ import random from "../../../utils/random.js";
const e = React.createElement;
function render(id) {
return e("span", { id: `${id}-content` }, ["Hello world"]);
function render(id, props = {}) {
return e("span", { id: `${id}-content`, ...props }, ["Hello world"]);
}
export default class AppearableComponent extends React.PureComponent {
constructor(props) {
super(props);
let element = null;
if (props.appear) {
if (props.useStyle) {
element = render(props.id, { style: {display: "none"}})
}
} else {
if (props.useStyle) {
element = render(props.id, { style: {display: "block" }})
} else {
element = render(props.id)
}
}
this.state = {
element: props.appear === true ? null : render(props.id)
element
};
}
handleClick() {
setTimeout(() => {
const props = this.props;
let element = null;
if (props.appear) {
if (props.useStyle) {
element = render(props.id, { style: {display: "block" }})
} else {
element = render(props.id)
}
} else {
if (props.useStyle) {
element = render(props.id, { style: {display: "none"}})
}
}
this.setState({
element: this.props.appear === true ? render(this.props.id) : null
element,
})
}, random())
}

View File

@ -48,13 +48,31 @@ export default class EventsPage extends React.Component {
title: "Appearable"
})
]),
]),
e("div", { className: "row" }, [
e("div", { className: "col-lg-4"}, [
e(Appearable, {
id: "wait-no-element",
appear: false,
title: "Disappearable"
})
])
]),
e("div", { className: "col-lg-4"}, [
e(Appearable, {
id: "wait-style",
appear: true,
title: "Appearable with style",
useStyle: true,
})
]),
e("div", { className: "col-lg-4"}, [
e(Appearable, {
id: "wait-no-style",
appear: false,
title: "Disappearable",
useStyle: true,
})
]),
])
])
}

View File

@ -0,0 +1,11 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET attrs = ATTR_GET(el, "style")
RETURN EXPECT("display: block;", attrs.style)

View File

@ -0,0 +1,17 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET prev = el.attributes.style
ATTR_REMOVE(el, "style")
WAIT(1000)
LET curr = el.attributes.style
RETURN prev == "display: block;" && curr == NONE ? "" : "expected attribute to be removed"

View File

@ -0,0 +1,17 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET prev = el.style
ATTR_SET(el, "style", "color: black;")
WAIT(1000)
LET curr = el.style
RETURN curr.color == "black" ? "" : "styles should be updated"

View File

@ -0,0 +1,14 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
ATTR_SET(el, { style: "color: black;", "data-ferret-x": "test" })
WAIT(1000)
RETURN el.style.color == "black" && el.attributes["data-ferret-x"] == "test" ? "" : "styles should be updated"

View File

@ -0,0 +1,11 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET val = STYLE_GET(el, "display")
RETURN val.display == "block" ? "" : "could not get style values"

View File

@ -0,0 +1,17 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET prev = el.style
STYLE_REMOVE(el, "display")
WAIT(1000)
LET curr = el.style
RETURN prev.display == "block" && curr.display == NONE ? "" : "expected style to be removed"

View File

@ -0,0 +1,17 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET prev = el.style
STYLE_SET(el, "color", "black")
WAIT(1000)
LET curr = el.style
RETURN curr.color == "black" ? "" : "styles should be updated"

View File

@ -0,0 +1,17 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET prev = el.style
STYLE_SET(el, { color: "black", "min-width": "100px", "background-color": "#11111" })
WAIT(1000)
LET curr = el.style
RETURN curr.color == "black" && curr["min-width"] == "100px" && curr["background-color"] == "#11111" ? "" : "styles should be updated"

View File

@ -47,8 +47,8 @@ type (
innerHTML values.String
innerText *common.LazyValue
value core.Value
rawAttrs []string
attributes *common.LazyValue
style *common.LazyValue
children []*HTMLElementIdentity
loadedChildren *common.LazyValue
}
@ -128,7 +128,6 @@ func LoadElement(
id,
node.Node.NodeType,
node.Node.NodeName,
node.Node.Attributes,
val,
innerHTML,
createChildrenArray(node.Node.Children),
@ -142,7 +141,6 @@ func NewHTMLElement(
id *HTMLElementIdentity,
nodeType int,
nodeName string,
attributes []string,
value string,
innerHTML values.String,
children []*HTMLElementIdentity,
@ -157,8 +155,8 @@ func NewHTMLElement(
el.nodeName = values.NewString(nodeName)
el.innerHTML = innerHTML
el.innerText = common.NewLazyValue(el.loadInnerText)
el.rawAttrs = attributes
el.attributes = common.NewLazyValue(el.loadAttrs)
el.style = common.NewLazyValue(el.parseStyle)
el.value = values.EmptyString
el.loadedChildren = common.NewLazyValue(el.loadChildren)
el.value = values.NewString(value)
@ -297,6 +295,99 @@ func (el *HTMLElement) Length() values.Int {
return values.NewInt(len(el.children))
}
func (el *HTMLElement) GetStyles(ctx context.Context) (*values.Object, error) {
val, err := el.style.Read(ctx)
if err != nil {
return values.NewObject(), err
}
if val == values.None {
return values.NewObject(), nil
}
styles := val.(*values.Object)
return styles.Copy().(*values.Object), nil
}
func (el *HTMLElement) GetStyle(ctx context.Context, name values.String) (core.Value, error) {
styles, err := el.style.Read(ctx)
if err != nil {
return values.None, err
}
val, found := styles.(*values.Object).Get(name)
if !found {
return values.None, nil
}
return val, nil
}
func (el *HTMLElement) SetStyles(ctx context.Context, styles *values.Object) error {
if styles == nil {
return nil
}
val, err := el.style.Read(ctx)
if err != nil {
return err
}
currentStyles := val.(*values.Object)
styles.ForEach(func(value core.Value, key string) bool {
currentStyles.Set(values.NewString(key), value)
return true
})
str := common.SerializeStyles(ctx, currentStyles)
return el.SetAttribute(ctx, "style", str)
}
func (el *HTMLElement) SetStyle(ctx context.Context, name values.String, value core.Value) error {
val, err := el.style.Read(ctx)
if err != nil {
return err
}
styles := val.(*values.Object)
styles.Set(name, value)
str := common.SerializeStyles(ctx, styles)
return el.SetAttribute(ctx, "style", str)
}
func (el *HTMLElement) RemoveStyle(ctx context.Context, names ...values.String) error {
if len(names) == 0 {
return nil
}
val, err := el.style.Read(ctx)
if err != nil {
return err
}
styles := val.(*values.Object)
for _, name := range names {
styles.Remove(name)
}
str := common.SerializeStyles(ctx, styles)
return el.SetAttribute(ctx, "style", str)
}
func (el *HTMLElement) GetAttributes(ctx context.Context) *values.Object {
val, err := el.attributes.Read(ctx)
@ -326,6 +417,18 @@ func (el *HTMLElement) GetAttribute(ctx context.Context, name values.String) cor
return val
}
func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object) error {
var err error
attrs.ForEach(func(value core.Value, key string) bool {
err = el.SetAttribute(ctx, values.NewString(key), values.NewString(value.String()))
return err == nil
})
return err
}
func (el *HTMLElement) SetAttribute(ctx context.Context, name, value values.String) error {
return el.client.DOM.SetAttributeValue(
ctx,
@ -333,6 +436,21 @@ func (el *HTMLElement) SetAttribute(ctx context.Context, name, value values.Stri
)
}
func (el *HTMLElement) RemoveAttribute(ctx context.Context, names ...values.String) error {
for _, name := range names {
err := el.client.DOM.RemoveAttribute(
ctx,
dom.NewRemoveAttributeArgs(el.id.nodeID, name.String()),
)
if err != nil {
return err
}
}
return nil
}
func (el *HTMLElement) GetChildNodes(ctx context.Context) core.Value {
val, err := el.loadedChildren.Read(ctx)
@ -940,8 +1058,14 @@ func (el *HTMLElement) loadInnerText(ctx context.Context) (core.Value, error) {
return parsed, nil
}
func (el *HTMLElement) loadAttrs(_ context.Context) (core.Value, error) {
return parseAttrs(el.rawAttrs), nil
func (el *HTMLElement) loadAttrs(ctx context.Context) (core.Value, error) {
repl, err := el.client.DOM.GetAttributes(ctx, dom.NewGetAttributesArgs(el.id.nodeID))
if err != nil {
return values.None, err
}
return parseAttrs(repl.Attributes), nil
}
func (el *HTMLElement) loadChildren(ctx context.Context) (core.Value, error) {
@ -973,6 +1097,20 @@ func (el *HTMLElement) loadChildren(ctx context.Context) (core.Value, error) {
return loaded, nil
}
func (el *HTMLElement) parseStyle(ctx context.Context) (core.Value, error) {
value := el.GetAttribute(ctx, "style")
if value == values.None {
return values.NewObject(), nil
}
if value.Type() != types.String {
return values.NewObject(), nil
}
return common.DeserializeStyles(value.(values.String))
}
func (el *HTMLElement) handlePageReload(_ context.Context, _ interface{}) {
el.Close()
}
@ -990,6 +1128,12 @@ func (el *HTMLElement) handleAttrModified(ctx context.Context, message interface
return
}
// they are not event loaded
// just ignore the event
if !el.attributes.Ready() {
return
}
el.attributes.Write(ctx, func(v core.Value, err error) {
if err != nil {
el.logError(err).Msg("failed to update element")
@ -997,6 +1141,10 @@ func (el *HTMLElement) handleAttrModified(ctx context.Context, message interface
return
}
if reply.Name == "style" {
el.style.Reset()
}
attrs, ok := v.(*values.Object)
if !ok {
@ -1033,6 +1181,10 @@ func (el *HTMLElement) handleAttrRemoved(ctx context.Context, message interface{
return
}
if reply.Name == "style" {
el.style.Reset()
}
attrs, ok := v.(*values.Object)
if !ok {

View File

@ -4,10 +4,10 @@ import (
"bytes"
"context"
"errors"
"github.com/MontFerret/ferret/pkg/drivers"
"math"
"strings"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/drivers/cdp/events"
"github.com/MontFerret/ferret/pkg/drivers/common"

View File

@ -59,6 +59,18 @@ func GetInElement(ctx context.Context, el drivers.HTMLElement, path []core.Value
}
return values.GetIn(ctx, attrs, path[1:])
case "style":
styles, err := el.GetStyles(ctx)
if err != nil {
return values.None, err
}
if len(path) == 1 {
return styles, nil
}
return values.GetIn(ctx, styles, path[1:])
default:
return GetInNode(ctx, el, path)
}

View File

@ -9,18 +9,18 @@ import (
)
type (
LazyFactory func(ctx context.Context) (core.Value, error)
LazyValueFactory func(ctx context.Context) (core.Value, error)
LazyValue struct {
sync.Mutex
factory LazyFactory
factory LazyValueFactory
ready bool
value core.Value
err error
}
)
func NewLazyValue(factory LazyFactory) *LazyValue {
func NewLazyValue(factory LazyValueFactory) *LazyValue {
lz := new(LazyValue)
lz.ready = false
lz.factory = factory

View File

@ -54,6 +54,15 @@ func SetInElement(ctx context.Context, el drivers.HTMLElement, path []core.Value
return err
}
curr := el.GetAttributes(ctx)
// remove all previous attributes
err = el.RemoveAttribute(ctx, curr.Keys()...)
if err != nil {
return err
}
obj := value.(*values.Object)
obj.ForEach(func(value core.Value, key string) bool {
err = el.SetAttribute(ctx, values.NewString(key), values.NewString(value.String()))
@ -61,6 +70,35 @@ func SetInElement(ctx context.Context, el drivers.HTMLElement, path []core.Value
return err == nil
})
return err
case "style":
if len(path) > 1 {
attrName := path[1]
return el.SetStyle(ctx, values.NewString(attrName.String()), value)
}
err := core.ValidateType(value, types.Object)
if err != nil {
return err
}
styles, err := el.GetStyles(ctx)
if err != nil {
return err
}
err = el.RemoveStyle(ctx, styles.Keys()...)
obj := value.(*values.Object)
obj.ForEach(func(value core.Value, key string) bool {
err = el.SetStyle(ctx, values.NewString(key), value)
return err == nil
})
return err
case "value":
if len(path) > 1 {

View File

@ -0,0 +1,103 @@
package common
import (
"bytes"
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"strconv"
"strings"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/gorilla/css/scanner"
)
func DeserializeStyles(input values.String) (*values.Object, error) {
styles := values.NewObject()
if input == values.EmptyString {
return styles, nil
}
s := scanner.New(input.String())
var name string
var value bytes.Buffer
var setValue = func() {
styles.Set(values.NewString(strings.TrimSpace(name)), values.NewString(strings.TrimSpace(value.String())))
name = ""
value.Reset()
}
for {
token := s.Next()
if token == nil {
break
}
if token.Type == scanner.TokenEOF {
break
}
if name == "" && token.Type == scanner.TokenIdent {
name = token.Value
// skip : and white spaces
for {
token = s.Next()
if token.Value != ":" && token.Type != scanner.TokenS {
break
}
}
}
switch token.Type {
case scanner.TokenChar:
// end of style declaration
if token.Value == ";" {
if name != "" {
setValue()
}
} else {
value.WriteString(token.Value)
}
case scanner.TokenNumber:
num, err := strconv.ParseFloat(token.Value, 64)
if err == nil {
styles.Set(values.NewString(name), values.NewFloat(num))
// reset prop
name = ""
value.Reset()
}
default:
value.WriteString(token.Value)
}
}
if name != "" && value.Len() > 0 {
setValue()
}
return styles, nil
}
func SerializeStyles(_ context.Context, styles *values.Object) values.String {
if styles == nil {
return values.EmptyString
}
var b bytes.Buffer
styles.ForEach(func(value core.Value, key string) bool {
b.WriteString(key)
b.WriteString(": ")
b.WriteString(value.String())
b.WriteString("; ")
return true
})
return values.NewString(b.String())
}

View File

@ -0,0 +1,102 @@
package common_test
import (
"bytes"
"github.com/MontFerret/ferret/pkg/drivers/common"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
type style struct {
raw string
name values.String
value core.Value
}
func TestDeserializeStyles(t *testing.T) {
Convey("DeserializeStyles", t, func() {
styles := []style{
{
raw: "min-height: 1.15",
name: "min-height",
value: values.NewFloat(1.15),
},
{
raw: "background-color: #4A154B",
name: "background-color",
value: values.NewString("#4A154B"),
},
{
raw: "font-size:26pt",
name: "font-size",
value: values.NewString("26pt"),
},
{
raw: "page-break-after:avoid",
name: "page-break-after",
value: values.NewString("avoid"),
},
{
raw: `font-family: Arial,"Helvetica Neue",Helvetica,sans-serif`,
name: "font-family",
value: values.NewString(`Arial,"Helvetica Neue",Helvetica,sans-serif`),
},
{
raw: "color: black",
name: "color",
value: values.NewString("black"),
},
{
raw: "display: inline-block",
name: "display",
value: values.NewString("inline-block"),
},
{
raw: "min-width: 50",
name: "min-width",
value: values.NewFloat(50),
},
}
Convey("Should parse a single style", func() {
for _, s := range styles {
out, err := common.DeserializeStyles(values.NewString(s.raw))
So(err, ShouldBeNil)
So(out, ShouldNotBeNil)
value, exists := out.Get(s.name)
So(bool(exists), ShouldBeTrue)
So(value.Compare(s.value) == 0, ShouldBeTrue)
}
})
Convey("Should parse multiple styles", func() {
var buff bytes.Buffer
for _, s := range styles {
buff.WriteString(s.raw)
buff.WriteString("; ")
}
out, err := common.DeserializeStyles(values.NewString(buff.String()))
So(err, ShouldBeNil)
So(out, ShouldNotBeNil)
So(int(out.Length()), ShouldEqual, len(styles))
for _, s := range styles {
value, exists := out.Get(s.name)
So(bool(exists), ShouldBeTrue)
So(value.Compare(s.value) == 0, ShouldBeTrue)
}
})
})
}

View File

@ -16,6 +16,7 @@ import (
type HTMLElement struct {
selection *goquery.Selection
attrs *values.Object
styles *values.Object
children *values.Array
}
@ -24,22 +25,22 @@ func NewHTMLElement(node *goquery.Selection) (drivers.HTMLElement, error) {
return nil, core.Error(core.ErrMissedArgument, "element selection")
}
return &HTMLElement{node, nil, nil}, nil
return &HTMLElement{node, nil, nil, nil}, nil
}
func (nd *HTMLElement) MarshalJSON() ([]byte, error) {
return json.Marshal(nd.InnerText(context.Background()).String())
func (el *HTMLElement) MarshalJSON() ([]byte, error) {
return json.Marshal(el.InnerText(context.Background()).String())
}
func (nd *HTMLElement) Type() core.Type {
func (el *HTMLElement) Type() core.Type {
return drivers.HTMLElementType
}
func (nd *HTMLElement) String() string {
return nd.InnerHTML(context.Background()).String()
func (el *HTMLElement) String() string {
return el.InnerHTML(context.Background()).String()
}
func (nd *HTMLElement) Compare(other core.Value) int64 {
func (el *HTMLElement) Compare(other core.Value) int64 {
switch other.Type() {
case drivers.HTMLElementType:
other := other.(drivers.HTMLElement)
@ -47,18 +48,18 @@ func (nd *HTMLElement) Compare(other core.Value) int64 {
ctx, fn := drivers.WithDefaultTimeout(context.Background())
defer fn()
return nd.InnerHTML(ctx).Compare(other.InnerHTML(ctx))
return el.InnerHTML(ctx).Compare(other.InnerHTML(ctx))
default:
return drivers.Compare(nd.Type(), other.Type())
return drivers.Compare(el.Type(), other.Type())
}
}
func (nd *HTMLElement) Unwrap() interface{} {
return nd.selection
func (el *HTMLElement) Unwrap() interface{} {
return el.selection
}
func (nd *HTMLElement) Hash() uint64 {
str, err := nd.selection.Html()
func (el *HTMLElement) Hash() uint64 {
str, err := el.selection.Html()
if err != nil {
return 0
@ -66,21 +67,21 @@ func (nd *HTMLElement) Hash() uint64 {
h := fnv.New64a()
h.Write([]byte(nd.Type().String()))
h.Write([]byte(el.Type().String()))
h.Write([]byte(":"))
h.Write([]byte(str))
return h.Sum64()
}
func (nd *HTMLElement) Copy() core.Value {
c, _ := NewHTMLElement(nd.selection.Clone())
func (el *HTMLElement) Copy() core.Value {
c, _ := NewHTMLElement(el.selection.Clone())
return c
}
func (nd *HTMLElement) NodeType() values.Int {
nodes := nd.selection.Nodes
func (el *HTMLElement) NodeType() values.Int {
nodes := el.selection.Nodes
if len(nodes) == 0 {
return 0
@ -89,24 +90,24 @@ func (nd *HTMLElement) NodeType() values.Int {
return values.NewInt(common.ToHTMLType(nodes[0].Type))
}
func (nd *HTMLElement) Close() error {
func (el *HTMLElement) Close() error {
return nil
}
func (nd *HTMLElement) NodeName() values.String {
return values.NewString(goquery.NodeName(nd.selection))
func (el *HTMLElement) NodeName() values.String {
return values.NewString(goquery.NodeName(el.selection))
}
func (nd *HTMLElement) Length() values.Int {
if nd.children == nil {
nd.children = nd.parseChildren()
func (el *HTMLElement) Length() values.Int {
if el.children == nil {
el.children = el.parseChildren()
}
return nd.children.Length()
return el.children.Length()
}
func (nd *HTMLElement) GetValue(_ context.Context) core.Value {
val, ok := nd.selection.Attr("value")
func (el *HTMLElement) GetValue(_ context.Context) core.Value {
val, ok := el.selection.Attr("value")
if ok {
return values.NewString(val)
@ -115,18 +116,18 @@ func (nd *HTMLElement) GetValue(_ context.Context) core.Value {
return values.EmptyString
}
func (nd *HTMLElement) SetValue(_ context.Context, value core.Value) error {
nd.selection.SetAttr("value", value.String())
func (el *HTMLElement) SetValue(_ context.Context, value core.Value) error {
el.selection.SetAttr("value", value.String())
return nil
}
func (nd *HTMLElement) InnerText(_ context.Context) values.String {
return values.NewString(nd.selection.Text())
func (el *HTMLElement) InnerText(_ context.Context) values.String {
return values.NewString(el.selection.Text())
}
func (nd *HTMLElement) InnerHTML(_ context.Context) values.String {
h, err := nd.selection.Html()
func (el *HTMLElement) InnerHTML(_ context.Context) values.String {
h, err := el.selection.Html()
if err != nil {
return values.EmptyString
@ -135,48 +136,140 @@ func (nd *HTMLElement) InnerHTML(_ context.Context) values.String {
return values.NewString(h)
}
func (nd *HTMLElement) GetAttributes(_ context.Context) *values.Object {
if nd.attrs == nil {
nd.attrs = nd.parseAttrs()
func (el *HTMLElement) GetStyles(ctx context.Context) (*values.Object, error) {
if err := el.ensureStyles(ctx); err != nil {
return values.NewObject(), err
}
return nd.attrs
return el.styles.Copy().(*values.Object), nil
}
func (nd *HTMLElement) GetAttribute(_ context.Context, name values.String) core.Value {
v, ok := nd.selection.Attr(name.String())
if ok {
return values.NewString(v)
func (el *HTMLElement) GetStyle(ctx context.Context, name values.String) (core.Value, error) {
if err := el.ensureStyles(ctx); err != nil {
return values.None, err
}
return values.None
return el.styles.MustGet(name), nil
}
func (nd *HTMLElement) SetAttribute(_ context.Context, name, value values.String) error {
nd.selection.SetAttr(string(name), string(value))
func (el *HTMLElement) SetStyle(ctx context.Context, name values.String, value core.Value) error {
if err := el.ensureStyles(ctx); err != nil {
return err
}
el.styles.Set(name, value)
str := common.SerializeStyles(ctx, el.styles)
return el.SetAttribute(ctx, "style", str)
}
func (el *HTMLElement) SetStyles(ctx context.Context, newStyles *values.Object) error {
if newStyles == nil {
return nil
}
if err := el.ensureStyles(ctx); err != nil {
return err
}
newStyles.ForEach(func(i core.Value, key string) bool {
el.styles.Set(values.NewString(key), i)
return true
})
str := common.SerializeStyles(ctx, el.styles)
return el.SetAttribute(ctx, "style", str)
}
func (el *HTMLElement) RemoveStyle(ctx context.Context, name ...values.String) error {
if len(name) == 0 {
return nil
}
if err := el.ensureStyles(ctx); err != nil {
return err
}
for _, s := range name {
el.styles.Remove(s)
}
str := common.SerializeStyles(ctx, el.styles)
return el.SetAttribute(ctx, "style", str)
}
func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object) error {
if attrs == nil {
return nil
}
el.ensureAttrs()
var err error
attrs.ForEach(func(value core.Value, key string) bool {
err = el.SetAttribute(ctx, values.NewString(key), values.NewString(value.String()))
return err == nil
})
return err
}
func (el *HTMLElement) GetAttributes(_ context.Context) *values.Object {
el.ensureAttrs()
return el.attrs.Copy().(*values.Object)
}
func (el *HTMLElement) GetAttribute(_ context.Context, name values.String) core.Value {
el.ensureAttrs()
return el.attrs.MustGet(name)
}
func (el *HTMLElement) SetAttribute(_ context.Context, name, value values.String) error {
el.ensureAttrs()
el.attrs.Set(name, value)
el.selection.SetAttr(string(name), string(value))
return nil
}
func (nd *HTMLElement) GetChildNodes(_ context.Context) core.Value {
if nd.children == nil {
nd.children = nd.parseChildren()
func (el *HTMLElement) RemoveAttribute(_ context.Context, name ...values.String) error {
el.ensureAttrs()
for _, attr := range name {
el.attrs.Remove(attr)
el.selection.RemoveAttr(attr.String())
}
return nd.children
return nil
}
func (nd *HTMLElement) GetChildNode(_ context.Context, idx values.Int) core.Value {
if nd.children == nil {
nd.children = nd.parseChildren()
func (el *HTMLElement) GetChildNodes(_ context.Context) core.Value {
if el.children == nil {
el.children = el.parseChildren()
}
return nd.children.Get(idx)
return el.children
}
func (nd *HTMLElement) QuerySelector(_ context.Context, selector values.String) core.Value {
selection := nd.selection.Find(selector.String())
func (el *HTMLElement) GetChildNode(_ context.Context, idx values.Int) core.Value {
if el.children == nil {
el.children = el.parseChildren()
}
return el.children.Get(idx)
}
func (el *HTMLElement) QuerySelector(_ context.Context, selector values.String) core.Value {
selection := el.selection.Find(selector.String())
if selection == nil {
return values.None
@ -191,8 +284,8 @@ func (nd *HTMLElement) QuerySelector(_ context.Context, selector values.String)
return res
}
func (nd *HTMLElement) QuerySelectorAll(_ context.Context, selector values.String) core.Value {
selection := nd.selection.Find(selector.String())
func (el *HTMLElement) QuerySelectorAll(_ context.Context, selector values.String) core.Value {
selection := el.selection.Find(selector.String())
if selection == nil {
return values.None
@ -211,8 +304,8 @@ func (nd *HTMLElement) QuerySelectorAll(_ context.Context, selector values.Strin
return arr
}
func (nd *HTMLElement) InnerHTMLBySelector(_ context.Context, selector values.String) values.String {
selection := nd.selection.Find(selector.String())
func (el *HTMLElement) InnerHTMLBySelector(_ context.Context, selector values.String) values.String {
selection := el.selection.Find(selector.String())
str, err := selection.Html()
@ -224,8 +317,8 @@ func (nd *HTMLElement) InnerHTMLBySelector(_ context.Context, selector values.St
return values.NewString(str)
}
func (nd *HTMLElement) InnerHTMLBySelectorAll(_ context.Context, selector values.String) *values.Array {
selection := nd.selection.Find(selector.String())
func (el *HTMLElement) InnerHTMLBySelectorAll(_ context.Context, selector values.String) *values.Array {
selection := el.selection.Find(selector.String())
arr := values.NewArray(selection.Length())
selection.Each(func(_ int, selection *goquery.Selection) {
@ -240,14 +333,14 @@ func (nd *HTMLElement) InnerHTMLBySelectorAll(_ context.Context, selector values
return arr
}
func (nd *HTMLElement) InnerTextBySelector(_ context.Context, selector values.String) values.String {
selection := nd.selection.Find(selector.String())
func (el *HTMLElement) InnerTextBySelector(_ context.Context, selector values.String) values.String {
selection := el.selection.Find(selector.String())
return values.NewString(selection.Text())
}
func (nd *HTMLElement) InnerTextBySelectorAll(_ context.Context, selector values.String) *values.Array {
selection := nd.selection.Find(selector.String())
func (el *HTMLElement) InnerTextBySelectorAll(_ context.Context, selector values.String) *values.Array {
selection := el.selection.Find(selector.String())
arr := values.NewArray(selection.Length())
selection.Each(func(_ int, selection *goquery.Selection) {
@ -257,8 +350,8 @@ func (nd *HTMLElement) InnerTextBySelectorAll(_ context.Context, selector values
return arr
}
func (nd *HTMLElement) CountBySelector(_ context.Context, selector values.String) values.Int {
selection := nd.selection.Find(selector.String())
func (el *HTMLElement) CountBySelector(_ context.Context, selector values.String) values.Int {
selection := el.selection.Find(selector.String())
if selection == nil {
return values.ZeroInt
@ -267,8 +360,8 @@ func (nd *HTMLElement) CountBySelector(_ context.Context, selector values.String
return values.NewInt(selection.Size())
}
func (nd *HTMLElement) ExistsBySelector(_ context.Context, selector values.String) values.Boolean {
selection := nd.selection.Closest(selector.String())
func (el *HTMLElement) ExistsBySelector(_ context.Context, selector values.String) values.Boolean {
selection := el.selection.Closest(selector.String())
if selection == nil {
return values.False
@ -277,47 +370,83 @@ func (nd *HTMLElement) ExistsBySelector(_ context.Context, selector values.Strin
return values.True
}
func (nd *HTMLElement) GetIn(ctx context.Context, path []core.Value) (core.Value, error) {
return common.GetInElement(ctx, nd, path)
func (el *HTMLElement) GetIn(ctx context.Context, path []core.Value) (core.Value, error) {
return common.GetInElement(ctx, el, path)
}
func (nd *HTMLElement) SetIn(ctx context.Context, path []core.Value, value core.Value) error {
return common.SetInElement(ctx, nd, path, value)
func (el *HTMLElement) SetIn(ctx context.Context, path []core.Value, value core.Value) error {
return common.SetInElement(ctx, el, path, value)
}
func (nd *HTMLElement) Iterate(_ context.Context) (core.Iterator, error) {
return common.NewIterator(nd)
func (el *HTMLElement) Iterate(_ context.Context) (core.Iterator, error) {
return common.NewIterator(el)
}
func (nd *HTMLElement) Click(_ context.Context) (values.Boolean, error) {
func (el *HTMLElement) Click(_ context.Context) (values.Boolean, error) {
return false, core.ErrNotSupported
}
func (nd *HTMLElement) Input(_ context.Context, _ core.Value, _ values.Int) error {
func (el *HTMLElement) Input(_ context.Context, _ core.Value, _ values.Int) error {
return core.ErrNotSupported
}
func (nd *HTMLElement) Select(_ context.Context, _ *values.Array) (*values.Array, error) {
func (el *HTMLElement) Select(_ context.Context, _ *values.Array) (*values.Array, error) {
return nil, core.ErrNotSupported
}
func (nd *HTMLElement) ScrollIntoView(_ context.Context) error {
func (el *HTMLElement) ScrollIntoView(_ context.Context) error {
return core.ErrNotSupported
}
func (nd *HTMLElement) Hover(_ context.Context) error {
func (el *HTMLElement) Hover(_ context.Context) error {
return core.ErrNotSupported
}
func (nd *HTMLElement) WaitForClass(_ context.Context, _ values.String, _ drivers.WaitEvent) error {
func (el *HTMLElement) WaitForClass(_ context.Context, _ values.String, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (nd *HTMLElement) parseAttrs() *values.Object {
func (el *HTMLElement) ensureStyles(ctx context.Context) error {
if el.styles == nil {
styles, err := el.parseStyles(ctx)
if err != nil {
return err
}
el.styles = styles
}
return nil
}
func (el *HTMLElement) parseStyles(ctx context.Context) (*values.Object, error) {
str := el.GetAttribute(ctx, "style")
if str == values.None {
return values.NewObject(), nil
}
styles, err := common.DeserializeStyles(values.NewString(str.String()))
if err != nil {
return nil, err
}
return styles, nil
}
func (el *HTMLElement) ensureAttrs() {
if el.attrs == nil {
el.attrs = el.parseAttrs()
}
}
func (el *HTMLElement) parseAttrs() *values.Object {
obj := values.NewObject()
for _, name := range common.Attributes {
val, ok := nd.selection.Attr(name)
val, ok := el.selection.Attr(name)
if ok {
obj.Set(values.NewString(name), values.NewString(val))
@ -327,8 +456,8 @@ func (nd *HTMLElement) parseAttrs() *values.Object {
return obj
}
func (nd *HTMLElement) parseChildren() *values.Array {
children := nd.selection.Children()
func (el *HTMLElement) parseChildren() *values.Array {
children := el.selection.Children()
arr := values.NewArray(10)

View File

@ -53,12 +53,26 @@ type (
SetValue(ctx context.Context, value core.Value) error
GetStyles(ctx context.Context) (*values.Object, error)
GetStyle(ctx context.Context, name values.String) (core.Value, error)
SetStyles(ctx context.Context, values *values.Object) error
SetStyle(ctx context.Context, name values.String, value core.Value) error
RemoveStyle(ctx context.Context, name ...values.String) error
GetAttributes(ctx context.Context) *values.Object
GetAttribute(ctx context.Context, name values.String) core.Value
SetAttributes(ctx context.Context, values *values.Object) error
SetAttribute(ctx context.Context, name, value values.String) error
RemoveAttribute(ctx context.Context, name ...values.String) error
InnerHTMLBySelector(ctx context.Context, selector values.String) values.String
InnerHTMLBySelectorAll(ctx context.Context, selector values.String) *values.Array

View File

@ -22,7 +22,7 @@ type (
KeyedCollection interface {
core.Value
Measurable
Keys() []string
Keys() []values.String
Get(key values.String) (core.Value, values.Boolean)
Set(key values.String, value core.Value)
}

View File

@ -10,7 +10,7 @@ type KeyedIterator struct {
valVar string
keyVar string
values KeyedCollection
keys []string
keys []values.String
pos int
}
@ -40,7 +40,7 @@ func (iterator *KeyedIterator) Next(_ context.Context, scope *core.Scope) (*core
}
if len(iterator.keys) > iterator.pos {
key := values.NewString(iterator.keys[iterator.pos])
key := iterator.keys[iterator.pos]
val, _ := iterator.values.Get(key)
iterator.pos++

View File

@ -80,10 +80,23 @@ func (t *Object) Compare(other core.Value) int64 {
var res int64
sortedT := sort.StringSlice(t.Keys())
tKeys := make([]string, 0, len(t.value))
for k := range t.value {
tKeys = append(tKeys, k)
}
sortedT := sort.StringSlice(tKeys)
sortedT.Sort()
sortedOther := sort.StringSlice(other.Keys())
otherKeys := make([]string, 0, other.Length())
other.ForEach(func(value core.Value, k string) bool {
otherKeys = append(otherKeys, k)
return true
})
sortedOther := sort.StringSlice(otherKeys)
sortedOther.Sort()
var tVal, otherVal core.Value
@ -178,11 +191,21 @@ func (t *Object) Length() Int {
return Int(len(t.value))
}
func (t *Object) Keys() []string {
keys := make([]string, 0, len(t.value))
func (t *Object) Keys() []String {
keys := make([]String, 0, len(t.value))
for k := range t.value {
keys = append(keys, k)
keys = append(keys, NewString(k))
}
return keys
}
func (t *Object) Values() []core.Value {
keys := make([]core.Value, 0, len(t.value))
for _, v := range t.value {
keys = append(keys, v)
}
return keys

View File

@ -21,7 +21,6 @@ func NewLib() map[string]core.Function {
"REMOVE_NTH": RemoveNth,
"REMOVE_VALUE": RemoveValue,
"REMOVE_VALUES": RemoveValues,
"REVERSE": Reverse,
"SHIFT": Shift,
"SLICE": Slice,
"SORTED": Sorted,

View File

@ -1,36 +0,0 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// Reverse return a new array with its elements reversed.
// @param array (Array) - Target array.
// @returns (Array) - A new array with its elements reversed.
func Reverse(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], types.Array)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
size := int(arr.Length())
result := values.NewArray(size)
for i := size - 1; i >= 0; i-- {
result.Push(arr.Get(values.NewInt(i)))
}
return result, nil
}

View File

@ -4,6 +4,7 @@ import "github.com/MontFerret/ferret/pkg/runtime/core"
func NewLib() map[string]core.Function {
return map[string]core.Function{
"LENGTH": Length,
"LENGTH": Length,
"REVERSE": Reverse,
}
}

View File

@ -0,0 +1,51 @@
package collections
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// Reverse returns the reverse of a given string or array value.
// @param text (String|Array) - The string or array to reverse.
// @returns (String|Array) - Returns a reversed version of a given value.
func Reverse(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
err = core.ValidateType(args[0], types.Array, types.String)
if err != nil {
return values.None, err
}
switch col := args[0].(type) {
case values.String:
runes := []rune(string(col))
size := len(runes)
// Reverse
for i := 0; i < size/2; i++ {
runes[i], runes[size-1-i] = runes[size-1-i], runes[i]
}
return values.NewString(string(runes)), nil
case *values.Array:
size := int(col.Length())
result := values.NewArray(size)
for i := size - 1; i >= 0; i-- {
result.Push(col.Get(values.NewInt(i)))
}
return result, nil
default:
return values.None, nil
}
}

View File

@ -1,14 +1,33 @@
package arrays_test
package collections_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/collections"
. "github.com/smartystreets/goconvey/convey"
)
func TestReverse(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = collections.Reverse(context.Background())
So(err, ShouldBeError)
})
})
Convey("Should reverse a text with right encoding", t, func() {
out, _ := collections.Reverse(
context.Background(),
values.NewString("The quick brown 狐 jumped over the lazy 犬"),
)
So(out, ShouldEqual, "犬 yzal eht revo depmuj 狐 nworb kciuq ehT")
})
Convey("Should return a copy of an array with reversed elements", t, func() {
arr := values.NewArrayWith(
values.NewInt(1),
@ -19,7 +38,7 @@ func TestReverse(t *testing.T) {
values.NewInt(6),
)
out, err := arrays.Reverse(
out, err := collections.Reverse(
context.Background(),
arr,
)
@ -31,7 +50,7 @@ func TestReverse(t *testing.T) {
Convey("Should return an empty array when there no elements in a source one", t, func() {
arr := values.NewArray(0)
out, err := arrays.Reverse(
out, err := collections.Reverse(
context.Background(),
arr,
)

View File

@ -0,0 +1,41 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// AttributeGet gets single or more attribute(s) of a given element.
// @param el (HTMLElement) - Target element.
// @param names (...String) - Attribute name(s).
// @returns Object - Key-value pairs of attribute values.
func AttributeGet(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
el, err := resolveElement(args[0])
if err != nil {
return values.None, err
}
names := args[1:]
result := values.NewObject()
attrs := el.GetAttributes(ctx)
for _, n := range names {
name := values.NewString(n.String())
val, exists := attrs.Get(name)
if exists {
result.Set(name, val)
}
}
return result, nil
}

View File

@ -0,0 +1,41 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// AttributeRemove removes single or more attribute(s) of a given element.
// @param el (HTMLElement) - Target element.
// @param names (...String) - Attribute name(s).
func AttributeRemove(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
el, err := resolveElement(args[0])
if err != nil {
return values.None, err
}
attrs := args[1:]
attrsStr := make([]values.String, 0, len(attrs))
for _, attr := range attrs {
str, ok := attr.(values.String)
if !ok {
return values.None, core.TypeError(attr.Type(), types.String)
}
attrsStr = append(attrsStr, str)
}
return values.None, el.RemoveAttribute(ctx, attrsStr...)
}

View File

@ -0,0 +1,50 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// AttributeSet sets or updates a single or more attribute(s) of a given element.
// @param el (HTMLElement) - Target element.
// @param nameOrObj (String | Object) - Attribute name or an object representing a key-value pair of attributes.
// @param value (String) - If a second parameter is a string value, this parameter represent an attribute value.
func AttributeSet(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
el, err := resolveElement(args[0])
if err != nil {
return values.None, err
}
switch arg1 := args[1].(type) {
case values.String:
// ATTR_SET(el, name, value)
err = core.ValidateArgs(args, 3, 3)
if err != nil {
return values.None, nil
}
arg2, ok := args[2].(values.String)
if !ok {
return values.None, core.TypeError(arg1.Type(), types.String, types.Object)
}
return values.None, el.SetAttribute(ctx, arg1, arg2)
case *values.Object:
// ATTR_SET(el, values)
return values.None, el.SetAttributes(ctx, arg1)
default:
return values.None, core.TypeError(arg1.Type(), types.String, types.Object)
}
}

View File

@ -14,6 +14,9 @@ const defaultTimeout = 5000
func NewLib() map[string]core.Function {
return map[string]core.Function{
"ATTR_GET": AttributeGet,
"ATTR_REMOVE": AttributeRemove,
"ATTR_SET": AttributeSet,
"CLICK": Click,
"CLICK_ALL": ClickAll,
"DOCUMENT": Document,
@ -40,6 +43,9 @@ func NewLib() map[string]core.Function {
"SCROLL_ELEMENT": ScrollInto,
"SCROLL_TOP": ScrollTop,
"SELECT": Select,
"STYLE_GET": StyleGet,
"STYLE_REMOVE": StyleRemove,
"STYLE_SET": StyleSet,
"WAIT_ELEMENT": WaitElement,
"WAIT_NO_ELEMENT": WaitNoElement,
"WAIT_CLASS": WaitClass,

View File

@ -0,0 +1,44 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// StyleGet gets single or more style attribute value(s) of a given element.
// @param el (HTMLElement) - Target element.
// @param names (...String) - Style name(s).
// @returns Object - Key-value pairs of style values.
func StyleGet(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
el, err := resolveElement(args[0])
if err != nil {
return values.None, err
}
names := args[1:]
result := values.NewObject()
for _, n := range names {
name := values.NewString(n.String())
val, err := el.GetStyle(ctx, name)
if err != nil {
return values.None, err
}
if val != values.None {
result.Set(name, val)
}
}
return result, nil
}

View File

@ -0,0 +1,41 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// StyleRemove removes single or more style attribute value(s) of a given element.
// @param el (HTMLElement) - Target element.
// @param names (...String) - Style name(s).
func StyleRemove(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
el, err := resolveElement(args[0])
if err != nil {
return values.None, err
}
attrs := args[1:]
attrsStr := make([]values.String, 0, len(attrs))
for _, attr := range attrs {
str, ok := attr.(values.String)
if !ok {
return values.None, core.TypeError(attr.Type(), types.String)
}
attrsStr = append(attrsStr, str)
}
return values.None, el.RemoveStyle(ctx, attrsStr...)
}

View File

@ -0,0 +1,44 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// StyleSet sets or updates a single or more style attribute value of a given element.
// @param el (HTMLElement) - Target element.
// @param nameOrObj (String | Object) - Style name or an object representing a key-value pair of attributes.
// @param value (String) - If a second parameter is a string value, this parameter represent a style value.
func StyleSet(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
el, err := resolveElement(args[0])
if err != nil {
return values.None, err
}
switch arg1 := args[1].(type) {
case values.String:
// STYLE_SET(el, name, value)
err = core.ValidateArgs(args, 3, 3)
if err != nil {
return values.None, nil
}
return values.None, el.SetStyle(ctx, arg1, args[2])
case *values.Object:
// STYLE_SET(el, values)
return values.None, el.SetStyles(ctx, arg1)
default:
return values.None, core.TypeError(arg1.Type(), types.String, types.Object)
}
}

View File

@ -1,6 +1,7 @@
package stdlib
import (
"fmt"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
"github.com/MontFerret/ferret/pkg/stdlib/collections"
@ -17,6 +18,10 @@ func NewLib() map[string]core.Function {
add := func(l map[string]core.Function) {
for name, fn := range l {
if _, exists := lib[name]; exists {
panic(fmt.Sprintf("%s function already exists", name))
}
lib[name] = fn
}
}

View File

@ -185,15 +185,15 @@ func isEqualObjects(obj1 *values.Object, obj2 *values.Object) bool {
var val2 core.Value
for _, key := range obj1.Keys() {
val1, _ = obj1.Get(values.NewString(key))
val2, _ = obj2.Get(values.NewString(key))
val1, _ = obj1.Get(key)
val2, _ = obj2.Get(key)
if val1.Compare(val2) != 0 {
return false
}
}
for _, key := range obj2.Keys() {
val1, _ = obj1.Get(values.NewString(key))
val2, _ = obj2.Get(values.NewString(key))
val1, _ = obj1.Get(key)
val2, _ = obj2.Get(key)
if val2.Compare(val1) != 0 {
return false
}

View File

@ -36,7 +36,15 @@ func Keys(_ context.Context, args ...core.Value) (core.Value, error) {
needSort = bool(args[1].(values.Boolean))
}
keys := sort.StringSlice(obj.Keys())
oKeys := make([]string, 0, obj.Length())
obj.ForEach(func(value core.Value, key string) bool {
oKeys = append(oKeys, key)
return true
})
keys := sort.StringSlice(oKeys)
keysArray := values.NewArray(len(keys))
if needSort {

View File

@ -22,7 +22,6 @@ func NewLib() map[string]core.Function {
"REGEXP_SPLIT": RegexSplit,
"REGEXP_TEST": RegexTest,
"REGEXP_REPLACE": RegexReplace,
"REVERSE": Reverse,
"RIGHT": Right,
"RTRIM": RTrim,
"SHA1": Sha1,

View File

@ -1,30 +0,0 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// Reverse returns the reverse of the string value.
// @param text (String) - The string to revers
// @returns (String) - Returns a reversed version of the string.
func Reverse(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
text := args[0].String()
runes := []rune(text)
size := len(runes)
// Reverse
for i := 0; i < size/2; i++ {
runes[i], runes[size-1-i] = runes[size-1-i], runes[i]
}
return values.NewString(string(runes)), nil
}

View File

@ -1,29 +0,0 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestReverse(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.Reverse(context.Background())
So(err, ShouldBeError)
})
})
Convey("Should reverse a text with right encoding", t, func() {
out, _ := strings.Reverse(
context.Background(),
values.NewString("The quick brown 狐 jumped over the lazy 犬"),
)
So(out, ShouldEqual, "犬 yzal eht revo depmuj 狐 nworb kciuq ehT")
})
}