Skip to content

Commit

Permalink
Test cases for functions for #336 (#363)
Browse files Browse the repository at this point in the history
* SUBSTITUTE
* ACCRINTM fix
* tests
* MID
  • Loading branch information
zgordan-vv authored and gunnsth committed Jan 8, 2020
1 parent fbcea8d commit 2cfb353
Show file tree
Hide file tree
Showing 4 changed files with 287 additions and 12 deletions.
3 changes: 3 additions & 0 deletions spreadsheet/formula/fnfinance.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@ func Accrintm(args []Result) Result {
if errResult.Type == ResultTypeError {
return errResult
}
if issueDate >= settlementDate {
return MakeErrorResultType(ErrorTypeNum, "Issue date should be earlier than settlement date")
}
if args[2].Type != ResultTypeNumber {
return MakeErrorResult("ACCRINTM requires rate to be number argument")
}
Expand Down
15 changes: 6 additions & 9 deletions spreadsheet/formula/fnmathtrig.go
Original file line number Diff line number Diff line change
Expand Up @@ -1376,23 +1376,20 @@ const (
// Round is an implementation of the Excel ROUND function that rounds a number
// to a specified number of digits.
func round(args []Result, mode rmode) Result {
if len(args) == 0 {
return MakeErrorResult("ROUND() requires at least one numeric arguments")
if len(args) != 2 {
return MakeErrorResult("ROUND() requires two numeric arguments")
}
// number to round
number := args[0].AsNumber()
if number.Type != ResultTypeNumber {
return MakeErrorResult("first argument to ROUND() must be a number")
}

digits := float64(0)
if len(args) > 1 {
digitArg := args[1].AsNumber()
if digitArg.Type != ResultTypeNumber {
return MakeErrorResult("second argument to ROUND() must be a number")
}
digits = digitArg.ValueNumber
digitArg := args[1].AsNumber()
if digitArg.Type != ResultTypeNumber {
return MakeErrorResult("second argument to ROUND() must be a number")
}
digits := digitArg.ValueNumber

v := number.ValueNumber

Expand Down
126 changes: 126 additions & 0 deletions spreadsheet/formula/fntext.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ func init() {
RegisterFunction("LEN", Len)
RegisterFunction("LENB", Len)
RegisterFunction("LOWER", Lower)
RegisterFunction("MID", Mid)
RegisterFunction("PROPER", Proper)
RegisterFunction("REPLACE", Replace)
RegisterFunction("REPT", Rept)
RegisterFunction("RIGHT", Right)
RegisterFunction("RIGHTB", Right)
RegisterFunction("SEARCH", Search)
RegisterFunctionComplex("SEARCHB", Searchb)
RegisterFunction("SUBSTITUTE", Substitute)
RegisterFunction("T", T)
RegisterFunction("TEXT", Text)
RegisterFunction("TEXTJOIN", TextJoin)
Expand Down Expand Up @@ -380,6 +382,43 @@ func lower(arg Result) Result {
}
}

// Mid is an implementation of the Excel MID function that returns a copy
// of the string with each word capitalized.
func Mid(args []Result) Result {
if len(args) != 3 {
return MakeErrorResult("MID requires three arguments")
}
if args[0].Type != ResultTypeString {
return MakeErrorResult("MID requires text to be a string")
}
text := args[0].ValueString
if args[1].Type != ResultTypeNumber {
return MakeErrorResult("MID requires start_num to be a number")
}
startNum := int(args[1].ValueNumber)
if startNum < 1 {
return MakeErrorResult("MID requires start_num to be more than 0")
}
if args[2].Type != ResultTypeNumber {
return MakeErrorResult("MID requires num_chars to be a number")
}
numChars := int(args[2].ValueNumber)
if numChars < 0 {
return MakeErrorResult("MID requires num_chars to be non negative")
}
l := len(text)
if startNum > l {
return MakeStringResult("")
}
startNum--
endNum := startNum + numChars
if endNum > l + 1 {
return MakeStringResult(text[startNum:])
} else {
return MakeStringResult(text[startNum:endNum])
}
}

// Proper is an implementation of the Excel PROPER function that returns a copy
// of the string with each word capitalized.
func Proper(args []Result) Result {
Expand Down Expand Up @@ -534,6 +573,67 @@ func Searchb(ctx Context, ev Evaluator, args []Result) Result {
return MakeErrorResultType(ErrorTypeValue, "Not found")
}

// Substitute is an implementation of the Excel SUBSTITUTE function.
func Substitute(args []Result) Result {
argsNum := len(args)
if argsNum != 3 && argsNum != 4 {
return MakeErrorResult("SUBSTITUTE requires three or four arguments")
}
text, errResult := getString(args[0], "SUBSTITUTE", "text")
if errResult.Type == ResultTypeError {
return errResult
}
oldText, errResult := getString(args[1], "SUBSTITUTE", "old text")
if errResult.Type == ResultTypeError {
return errResult
}
newText, errResult := getString(args[2], "SUBSTITUTE", "new text")
if errResult.Type == ResultTypeError {
return errResult
}
instanceNum := 0
if argsNum == 3 {
return MakeStringResult(strings.Replace(text, oldText, newText, -1))
} else {
instanceNumF, errResult := getNumber(args[3], "SUBSTITUTE", "instance_num")
if errResult.Type == ResultTypeError {
return errResult
}
instanceNum = int(instanceNumF)
if instanceNum < 1 {
return MakeErrorResult("instance_num should be more than zero")
}
textCopy := text
countdown := instanceNum
pos := -1
l := len(oldText)
thrownTotal := 0
for {
countdown--
index := strings.Index(textCopy, oldText)
if index == -1 {
pos = -1
break
} else {
pos = index + thrownTotal
if countdown == 0 {
break
}
thrown := l + index
thrownTotal += thrown
textCopy = textCopy[thrown:]
}
}
if pos == -1 {
return MakeStringResult(text)
} else {
pre := text[:pos]
post := text[pos+l:]
return MakeStringResult(pre + newText + post)
}
}
}

// T is an implementation of the Excel T function that returns whether the
// argument is text.
func T(args []Result) Result {
Expand Down Expand Up @@ -752,3 +852,29 @@ func Text(args []Result) Result {
return MakeErrorResult("Incorrect argument for TEXT")
}
}

func getString(arg Result, funcName, argName string) (string, Result) {
switch arg.Type {
case ResultTypeString, ResultTypeNumber, ResultTypeEmpty:
return arg.Value(), empty
default:
return "", MakeErrorResult(funcName + " requires " + argName + " to be a number or string")
}
}

func getNumber(arg Result, funcName, argName string) (float64, Result) {
switch arg.Type {
case ResultTypeEmpty:
return 0, empty
case ResultTypeNumber:
return arg.ValueNumber, empty
case ResultTypeString:
f, err := strconv.ParseFloat(arg.ValueString, 64)
if err != nil {
return 0, MakeErrorResult(argName + " should be a number for " + funcName)
}
return f, empty
default:
return 0, MakeErrorResult(funcName + " requires " + argName + " to be a number or empty")
}
}
155 changes: 152 additions & 3 deletions spreadsheet/formula/functions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,29 @@ func TestMatch(t *testing.T) {
runTests(t, ctx, td)
}

func TestMaxA(t *testing.T) {
func TestMax(t *testing.T) {
ss := spreadsheet.New()
sheet := ss.AddSheet()

sheet.Cell("A1").SetNumber(0.1)
sheet.Cell("B1").SetNumber(0.2)

sheet.Cell("A2").SetNumber(0.4)
sheet.Cell("B2").SetNumber(0.8)

sheet.Cell("A3").SetBool(true)
sheet.Cell("B3").SetBool(false)

ctx := sheet.FormulaContext()

td := []testStruct{
{`MAX(A1:B3)`, `0.8 ResultTypeNumber`},
}

runTests(t, ctx, td)
}

func TestMaxA(t *testing.T) {
ss := spreadsheet.New()
sheet := ss.AddSheet()

Expand All @@ -587,8 +608,29 @@ func TestMaxA(t *testing.T) {
runTests(t, ctx, td)
}

func TestMinA(t *testing.T) {
func TestMin(t *testing.T) {
ss := spreadsheet.New()
sheet := ss.AddSheet()

sheet.Cell("A1").SetNumber(0.1)
sheet.Cell("B1").SetNumber(0.2)

sheet.Cell("A2").SetNumber(0.4)
sheet.Cell("B2").SetNumber(0.8)

sheet.Cell("A3").SetBool(true)
sheet.Cell("B3").SetBool(false)

ctx := sheet.FormulaContext()

td := []testStruct{
{`MIN(A1:B3)`, `0.1 ResultTypeNumber`},
}

runTests(t, ctx, td)
}

func TestMinA(t *testing.T) {
ss := spreadsheet.New()
sheet := ss.AddSheet()

Expand All @@ -611,7 +653,6 @@ func TestMinA(t *testing.T) {
}

func TestIfs(t *testing.T) {

ss := spreadsheet.New()
sheet := ss.AddSheet()

Expand Down Expand Up @@ -2742,3 +2783,111 @@ func TestYieldmat(t *testing.T) {

runTests(t, ctx, td)
}

func TestMid(t *testing.T) {
ss := spreadsheet.New()
sheet := ss.AddSheet()

ctx := sheet.FormulaContext()

td := []testStruct{
{`=MID("Fluid Flow",1,5)`, `Fluid ResultTypeString`},
{`=MID("Fluid Flow",7,20)`, `Flow ResultTypeString`},
{`=MID("Fluid Flow",20,5)`, ` ResultTypeString`},
{`=MID("Fluid Flow",1,0)`, ` ResultTypeString`},
{`=MID("Fluid Flow",0,5)`, `#VALUE! ResultTypeError`},
{`=MID("Fluid Flow",7,-20)`, `#VALUE! ResultTypeError`},
}

runTests(t, ctx, td)
}

func TestSubstitute(t *testing.T) {
ss := spreadsheet.New()
sheet := ss.AddSheet()

sheet.Cell("A1").SetString("Hello Earth Earth Earth")

ctx := sheet.FormulaContext()

td := []testStruct{
{`=SUBSTITUTE(A1,"Earth","Krypton",1)`, `Hello Krypton Earth Earth ResultTypeString`},
{`=SUBSTITUTE(A1,"Earth","Krypton",2)`, `Hello Earth Krypton Earth ResultTypeString`},
{`=SUBSTITUTE(A1,"Earth","Krypton",3)`, `Hello Earth Earth Krypton ResultTypeString`},
{`=SUBSTITUTE(A1,"Earth","Krypton",4)`, `Hello Earth Earth Earth ResultTypeString`},
{`=SUBSTITUTE(A1,"Earth","Krypton")`, `Hello Krypton Krypton Krypton ResultTypeString`},
{`=SUBSTITUTE(A1,"World","Krypton")`, `Hello Earth Earth Earth ResultTypeString`},
{`=SUBSTITUTE(A1,"Earth","Krypton",0)`, `#VALUE! ResultTypeError`},
}

runTests(t, ctx, td)
}

func TestAnd(t *testing.T) {
ss := spreadsheet.New()
sheet := ss.AddSheet()

ctx := sheet.FormulaContext()

td := []testStruct{
{`=AND(FALSE,FALSE)`, `0 ResultTypeNumber`},
{`=AND(TRUE,FALSE)`, `0 ResultTypeNumber`},
{`=AND(FALSE,TRUE)`, `0 ResultTypeNumber`},
{`=AND(TRUE,TRUE)`, `1 ResultTypeNumber`},
}

runTests(t, ctx, td)
}

func TestIferror(t *testing.T) {
ss := spreadsheet.New()
sheet := ss.AddSheet()

ctx := sheet.FormulaContext()

td := []testStruct{
{`=IFERROR("No error","ERROR")`, `No error ResultTypeString`},
{`=IFERROR(1/0,"ERROR")`, `ERROR ResultTypeString`},
}

runTests(t, ctx, td)
}

func TestChar(t *testing.T) {
ss := spreadsheet.New()
sheet := ss.AddSheet()

ctx := sheet.FormulaContext()

td := []testStruct{
{`=CHAR(65)`, `A ResultTypeString`},
{`=CHAR(255)`, `ÿ ResultTypeString`},
{`=CHAR(1000)`, `#VALUE! ResultTypeError`},
{`=CHAR("invalid")`, `#VALUE! ResultTypeError`},
}

runTests(t, ctx, td)
}

func TestRound(t *testing.T) {
ss := spreadsheet.New()
sheet := ss.AddSheet()

ctx := sheet.FormulaContext()

td := []testStruct{
{`=ROUND(2.14,1)`, `2.1 ResultTypeNumber`},
{`=ROUND(2.16,1)`, `2.2 ResultTypeNumber`},
{`=ROUND(-2.14,1)`, `-2.1 ResultTypeNumber`},
{`=ROUND(-2.16,1)`, `-2.2 ResultTypeNumber`},
{`=ROUND(21.5,-1)`, `20 ResultTypeNumber`},
{`=ROUND(21.5,-2)`, `0 ResultTypeNumber`},
{`=ROUND(-55.5,-1)`, `-60 ResultTypeNumber`},
{`=ROUND(-55.5,-2)`, `-100 ResultTypeNumber`},
{`=ROUND(-55.5,0)`, `-56 ResultTypeNumber`},
{`=ROUND(-55.4,)`, `-55 ResultTypeNumber`},
{`=ROUND(-55.4)`, `#VALUE! ResultTypeError`},
}

runTests(t, ctx, td)
}

0 comments on commit 2cfb353

Please sign in to comment.