Implement HTML writer #33
No files matched your search
@@ -13,6 +13,9 @@ import (
|
|||||||
"github.com/nmoniz/any2anexoj/internal"
|
"github.com/nmoniz/any2anexoj/internal"
|
||||||
"github.com/nmoniz/any2anexoj/internal/ofigi"
|
"github.com/nmoniz/any2anexoj/internal/ofigi"
|
||||||
"github.com/nmoniz/any2anexoj/internal/trading212"
|
"github.com/nmoniz/any2anexoj/internal/trading212"
|
||||||
|
"github.com/nmoniz/any2anexoj/internal/writing"
|
||||||
|
"github.com/nmoniz/any2anexoj/internal/writing/csv"
|
||||||
|
"github.com/nmoniz/any2anexoj/internal/writing/pretty"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
@@ -98,13 +101,13 @@ func runWithIO(ctx context.Context, stdin io.Reader, stdout io.Writer) error {
|
|||||||
|
|
||||||
switch *format {
|
switch *format {
|
||||||
case "csv":
|
case "csv":
|
||||||
return NewCSVWriter(stdout).Render(writer)
|
return csv.NewCSVWriter(stdout).Render(writer)
|
||||||
case "table":
|
case "table":
|
||||||
loc, err := NewLocalizer(*lang)
|
loc, err := writing.NewLocalizer(*lang)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create localizer: %w", err)
|
return fmt.Errorf("create localizer: %w", err)
|
||||||
}
|
}
|
||||||
NewPrettyPrinter(stdout, loc).Render(writer)
|
pretty.NewPrettyPrinter(stdout, loc).Render(writer)
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported format %q: must be table or csv", *format)
|
return fmt.Errorf("unsupported format %q: must be table or csv", *format)
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
package main
|
// Package csv renders an internal.AggregatorWriter as the CSV schema
|
||||||
|
// expected by Modelo 3 do Anexo J.
|
||||||
|
package csv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package csv_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nmoniz/any2anexoj/internal"
|
||||||
|
"github.com/nmoniz/any2anexoj/internal/writing/csv"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCSVWriter_Render(t *testing.T) {
|
||||||
|
aw := internal.NewAggregatorWriter()
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
items := []internal.ReportItem{
|
||||||
|
{
|
||||||
|
Symbol: "AAPL",
|
||||||
|
Nature: internal.NatureG01,
|
||||||
|
BrokerCountry: 826, // United Kingdom
|
||||||
|
AssetCountry: 840, // United States
|
||||||
|
BuyValue: decimal.NewFromFloat(100.50),
|
||||||
|
BuyTimestamp: time.Date(2023, 1, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
SellValue: decimal.NewFromFloat(150.75),
|
||||||
|
SellTimestamp: time.Date(2023, 6, 20, 0, 0, 0, 0, time.UTC),
|
||||||
|
Fees: decimal.NewFromFloat(2.50),
|
||||||
|
Taxes: decimal.NewFromFloat(5.00),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Symbol: "VWRL",
|
||||||
|
Nature: internal.NatureG20,
|
||||||
|
BrokerCountry: 196, // Portugal equivalent demo
|
||||||
|
AssetCountry: 372, // Ireland
|
||||||
|
BuyValue: decimal.NewFromFloat(200.00),
|
||||||
|
BuyTimestamp: time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
SellValue: decimal.NewFromFloat(250.25),
|
||||||
|
SellTimestamp: time.Date(2024, 7, 10, 0, 0, 0, 0, time.UTC),
|
||||||
|
Fees: decimal.NewFromFloat(1.00),
|
||||||
|
Taxes: decimal.NewFromFloat(0.00),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ri := range items {
|
||||||
|
if err := aw.Write(ctx, ri); err != nil {
|
||||||
|
t.Fatalf("write report item: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := csv.NewCSVWriter(&buf).Render(aw); err != nil {
|
||||||
|
t.Fatalf("render: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := buf.String()
|
||||||
|
|
||||||
|
wantHeader := "source_country,code,realization_year,realization_month,realization_day,realization_value,acquisition_year,acquisition_month,acquisition_day,acquisition_value,expenses,foreign_tax_paid,counter_country"
|
||||||
|
if !strings.HasPrefix(out, wantHeader+"\n") {
|
||||||
|
t.Errorf("expected header row %q, got output:\n%s", wantHeader, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantRow1 := "840,G01,2023,6,20,150.75,2023,1,15,100.50,2.50,5.00,826"
|
||||||
|
if !strings.Contains(out, wantRow1+"\n") {
|
||||||
|
t.Errorf("expected row 1 %q in output:\n%s", wantRow1, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantRow2 := "372,G20,2024,7,10,250.25,2024,2,1,200.00,1.00,0.00,196"
|
||||||
|
if !strings.Contains(out, wantRow2+"\n") {
|
||||||
|
t.Errorf("expected row 2 %q in output:\n%s", wantRow2, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the header only appears once and we have exactly 1 header + 2 data rows.
|
||||||
|
lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
|
||||||
|
if got, want := len(lines), 3; got != want {
|
||||||
|
t.Errorf("expected %d lines (1 header + 2 rows), got %d:\n%s", want, got, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
|
<<<<<<<< HEAD:cmd/any2anexoj-cli/localizer.go
|
||||||
package main
|
package main
|
||||||
|
========
|
||||||
|
package writing
|
||||||
|
>>>>>>>> 6ba3d78 (writers in writing package):internal/writing/localizer.go
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
@@ -1,6 +1,16 @@
|
|||||||
|
<<<<<<<< HEAD:cmd/any2anexoj-cli/localizer_test.go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
========
|
||||||
|
package writing_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nmoniz/any2anexoj/internal/writing"
|
||||||
|
)
|
||||||
|
>>>>>>>> 6ba3d78 (writers in writing package):internal/writing/localizer_test.go
|
||||||
|
|
||||||
func TestNewLocalizer(t *testing.T) {
|
func TestNewLocalizer(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -15,7 +25,11 @@ func TestNewLocalizer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
<<<<<<<< HEAD:cmd/any2anexoj-cli/localizer_test.go
|
||||||
_, err := NewLocalizer(tt.lang)
|
_, err := NewLocalizer(tt.lang)
|
||||||
|
========
|
||||||
|
_, err := writing.NewLocalizer(tt.lang)
|
||||||
|
>>>>>>>> 6ba3d78 (writers in writing package):internal/writing/localizer_test.go
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("want success call but failed: %v", err)
|
t.Fatalf("want success call but failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
|
<<<<<<<< HEAD:cmd/any2anexoj-cli/pretty_printer.go
|
||||||
package main
|
package main
|
||||||
|
========
|
||||||
|
package pretty
|
||||||
|
>>>>>>>> 6ba3d78 (writers in writing package):internal/writing/pretty/printer.go
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -11,20 +15,35 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// PrettyPrinter writes a simple, human readable, table row to the provided io.Writer for each
|
// PrettyPrinter writes a simple, human readable, table row to the provided io.Writer for each
|
||||||
// ReportItem received.
|
// ReportItem received. Render and RenderHTML each build a fresh table.Writer so that
|
||||||
|
// invoking them (in any order or repeatedly) on the same instance never duplicates rows.
|
||||||
type PrettyPrinter struct {
|
type PrettyPrinter struct {
|
||||||
table table.Writer
|
|
||||||
output io.Writer
|
output io.Writer
|
||||||
translator Translator
|
translator Translator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<<< HEAD:cmd/any2anexoj-cli/pretty_printer.go
|
||||||
type Translator interface {
|
type Translator interface {
|
||||||
Translate(key string, count int, values map[string]any) string
|
Translate(key string, count int, values map[string]any) string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPrettyPrinter(w io.Writer, tr Translator) *PrettyPrinter {
|
func NewPrettyPrinter(w io.Writer, tr Translator) *PrettyPrinter {
|
||||||
|
========
|
||||||
|
func NewPrettyPrinter(w io.Writer, tr translator) *PrettyPrinter {
|
||||||
|
return &PrettyPrinter{
|
||||||
|
output: w,
|
||||||
|
translator: tr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildTable constructs a fully configured table.Writer from the items in aw.
|
||||||
|
// Each call returns a new table.Writer so callers can render the same data
|
||||||
|
// multiple times without duplicating rows. The returned table is not wired to
|
||||||
|
// pp.output via an output mirror; callers that want the rendered output
|
||||||
|
// written to pp.output must set the mirror themselves before calling .Render().
|
||||||
|
func (pp *PrettyPrinter) buildTable(aw *internal.AggregatorWriter) table.Writer {
|
||||||
|
>>>>>>>> 6ba3d78 (writers in writing package):internal/writing/pretty/printer.go
|
||||||
tw := table.NewWriter()
|
tw := table.NewWriter()
|
||||||
tw.SetOutputMirror(w)
|
|
||||||
tw.SetAutoIndex(true)
|
tw.SetAutoIndex(true)
|
||||||
tw.SetStyle(table.StyleLight)
|
tw.SetStyle(table.StyleLight)
|
||||||
tw.SetColumnConfigs([]table.ColumnConfig{
|
tw.SetColumnConfigs([]table.ColumnConfig{
|
||||||
@@ -43,14 +62,6 @@ func NewPrettyPrinter(w io.Writer, tr Translator) *PrettyPrinter {
|
|||||||
colCountry(13),
|
colCountry(13),
|
||||||
})
|
})
|
||||||
|
|
||||||
return &PrettyPrinter{
|
|
||||||
table: tw,
|
|
||||||
output: w,
|
|
||||||
translator: tr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *PrettyPrinter) Render(aw *internal.AggregatorWriter) {
|
|
||||||
realizationTxt := pp.translator.Translate("realization", 1, nil)
|
realizationTxt := pp.translator.Translate("realization", 1, nil)
|
||||||
acquisitionTxt := pp.translator.Translate("acquisition", 1, nil)
|
acquisitionTxt := pp.translator.Translate("acquisition", 1, nil)
|
||||||
yearTxt := pp.translator.Translate("year", 1, nil)
|
yearTxt := pp.translator.Translate("year", 1, nil)
|
||||||
@@ -58,8 +69,8 @@ func (pp *PrettyPrinter) Render(aw *internal.AggregatorWriter) {
|
|||||||
dayTxt := pp.translator.Translate("day", 1, nil)
|
dayTxt := pp.translator.Translate("day", 1, nil)
|
||||||
valorTxt := pp.translator.Translate("value", 1, nil)
|
valorTxt := pp.translator.Translate("value", 1, nil)
|
||||||
|
|
||||||
pp.table.AppendHeader(table.Row{"", "", realizationTxt, realizationTxt, realizationTxt, realizationTxt, acquisitionTxt, acquisitionTxt, acquisitionTxt, acquisitionTxt, "", "", ""}, table.RowConfig{AutoMerge: true})
|
tw.AppendHeader(table.Row{"", "", realizationTxt, realizationTxt, realizationTxt, realizationTxt, acquisitionTxt, acquisitionTxt, acquisitionTxt, acquisitionTxt, "", "", ""}, table.RowConfig{AutoMerge: true})
|
||||||
pp.table.AppendHeader(table.Row{
|
tw.AppendHeader(table.Row{
|
||||||
pp.translator.Translate("source_country", 1, nil), pp.translator.Translate("code", 1, nil),
|
pp.translator.Translate("source_country", 1, nil), pp.translator.Translate("code", 1, nil),
|
||||||
yearTxt, monthTxt, dayTxt, valorTxt,
|
yearTxt, monthTxt, dayTxt, valorTxt,
|
||||||
yearTxt, monthTxt, dayTxt, valorTxt,
|
yearTxt, monthTxt, dayTxt, valorTxt,
|
||||||
@@ -67,7 +78,7 @@ func (pp *PrettyPrinter) Render(aw *internal.AggregatorWriter) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
for ri := range aw.Iter() {
|
for ri := range aw.Iter() {
|
||||||
pp.table.AppendRow(table.Row{
|
tw.AppendRow(table.Row{
|
||||||
ri.AssetCountry, ri.Nature,
|
ri.AssetCountry, ri.Nature,
|
||||||
ri.SellTimestamp.Year(), int(ri.SellTimestamp.Month()), ri.SellTimestamp.Day(), ri.SellValue.StringFixed(2),
|
ri.SellTimestamp.Year(), int(ri.SellTimestamp.Month()), ri.SellTimestamp.Day(), ri.SellValue.StringFixed(2),
|
||||||
ri.BuyTimestamp.Year(), int(ri.BuyTimestamp.Month()), ri.BuyTimestamp.Day(), ri.BuyValue.StringFixed(2),
|
ri.BuyTimestamp.Year(), int(ri.BuyTimestamp.Month()), ri.BuyTimestamp.Day(), ri.BuyValue.StringFixed(2),
|
||||||
@@ -75,8 +86,29 @@ func (pp *PrettyPrinter) Render(aw *internal.AggregatorWriter) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pp.table.AppendFooter(table.Row{"SUM", "SUM", "SUM", "SUM", "SUM", aw.TotalEarned(), "", "", "", aw.TotalSpent(), aw.TotalFees(), aw.TotalTaxes()}, table.RowConfig{AutoMerge: true, AutoMergeAlign: text.AlignRight})
|
sumTxt := pp.translator.Translate("sum", 1, nil)
|
||||||
pp.table.Render()
|
tw.AppendFooter(table.Row{sumTxt, sumTxt, sumTxt, sumTxt, sumTxt, aw.TotalEarned(), "", "", "", aw.TotalSpent(), aw.TotalFees(), aw.TotalTaxes()}, table.RowConfig{AutoMerge: true, AutoMergeAlign: text.AlignRight})
|
||||||
|
|
||||||
|
return tw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *PrettyPrinter) Render(aw *internal.AggregatorWriter) {
|
||||||
|
t := pp.buildTable(aw)
|
||||||
|
t.SetOutputMirror(pp.output)
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderHTML builds a fresh table from aw and renders it as an HTML string
|
||||||
|
// using go-pretty's HTMLOptions.
|
||||||
|
func (pp *PrettyPrinter) RenderHTML(aw *internal.AggregatorWriter) string {
|
||||||
|
t := pp.buildTable(aw)
|
||||||
|
t.Style().HTML = table.HTMLOptions{
|
||||||
|
CSSClass: "table table-striped table-hover table-bordered",
|
||||||
|
EscapeText: true,
|
||||||
|
Newline: "<br/>",
|
||||||
|
EmptyColumn: " ",
|
||||||
|
}
|
||||||
|
return t.RenderHTML()
|
||||||
}
|
}
|
||||||
|
|
||||||
func colEuros(n int) table.ColumnConfig {
|
func colEuros(n int) table.ColumnConfig {
|
||||||
+87
-1
@@ -1,11 +1,21 @@
|
|||||||
|
<<<<<<<< HEAD:cmd/any2anexoj-cli/pretty_printer_test.go
|
||||||
package main
|
package main
|
||||||
|
========
|
||||||
|
package pretty_test
|
||||||
|
>>>>>>>> 6ba3d78 (writers in writing package):internal/writing/pretty/printer_test.go
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nmoniz/any2anexoj/internal"
|
"github.com/nmoniz/any2anexoj/internal"
|
||||||
|
<<<<<<<< HEAD:cmd/any2anexoj-cli/pretty_printer_test.go
|
||||||
|
========
|
||||||
|
"github.com/nmoniz/any2anexoj/internal/writing"
|
||||||
|
"github.com/nmoniz/any2anexoj/internal/writing/pretty"
|
||||||
|
>>>>>>>> 6ba3d78 (writers in writing package):internal/writing/pretty/printer_test.go
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,14 +58,22 @@ func TestPrettyPrinter_Render(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create English localizer
|
// Create English localizer
|
||||||
|
<<<<<<<< HEAD:cmd/any2anexoj-cli/pretty_printer_test.go
|
||||||
localizer, err := NewLocalizer("en")
|
localizer, err := NewLocalizer("en")
|
||||||
|
========
|
||||||
|
localizer, err := writing.NewLocalizer("en")
|
||||||
|
>>>>>>>> 6ba3d78 (writers in writing package):internal/writing/pretty/printer_test.go
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create localizer: %v", err)
|
t.Fatalf("failed to create localizer: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create pretty printer with buffer
|
// Create writing printer with buffer
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
<<<<<<<< HEAD:cmd/any2anexoj-cli/pretty_printer_test.go
|
||||||
pp := NewPrettyPrinter(&buf, localizer)
|
pp := NewPrettyPrinter(&buf, localizer)
|
||||||
|
========
|
||||||
|
pp := pretty.NewPrettyPrinter(&buf, localizer)
|
||||||
|
>>>>>>>> 6ba3d78 (writers in writing package):internal/writing/pretty/printer_test.go
|
||||||
|
|
||||||
// Render the table
|
// Render the table
|
||||||
pp.Render(aw)
|
pp.Render(aw)
|
||||||
@@ -81,3 +99,71 @@ func TestPrettyPrinter_Render(t *testing.T) {
|
|||||||
t.Errorf("PrettyPrinter.Render() output doesn't match expected.\n\nGot:\n%s\n\nWant:\n%s", got, want)
|
t.Errorf("PrettyPrinter.Render() output doesn't match expected.\n\nGot:\n%s\n\nWant:\n%s", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderHTML(t *testing.T) {
|
||||||
|
// Create test data
|
||||||
|
aw := internal.NewAggregatorWriter()
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
err := aw.Write(ctx, internal.ReportItem{
|
||||||
|
Symbol: "AAPL",
|
||||||
|
Nature: internal.NatureG01,
|
||||||
|
BrokerCountry: 826, // United Kingdom
|
||||||
|
AssetCountry: 840, // United States
|
||||||
|
BuyValue: decimal.NewFromFloat(100.50),
|
||||||
|
BuyTimestamp: time.Date(2023, 1, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
SellValue: decimal.NewFromFloat(150.75),
|
||||||
|
SellTimestamp: time.Date(2023, 6, 20, 0, 0, 0, 0, time.UTC),
|
||||||
|
Fees: decimal.NewFromFloat(2.50),
|
||||||
|
Taxes: decimal.NewFromFloat(5.00),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to write report item: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Portuguese localizer so the header text is in pt
|
||||||
|
localizer, err := writing.NewLocalizer("pt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create localizer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output buffer used only to satisfy the constructor contract; RenderHTML
|
||||||
|
// must not write to it.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
pp := pretty.NewPrettyPrinter(&buf, localizer)
|
||||||
|
|
||||||
|
html := pp.RenderHTML(aw)
|
||||||
|
|
||||||
|
// RenderHTML must not side-write to pp.output.
|
||||||
|
if buf.Len() != 0 {
|
||||||
|
t.Errorf("RenderHTML should not write to pp.output, but got %d bytes: %q", buf.Len(), buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Structural assertions.
|
||||||
|
wantSubstrings := []string{
|
||||||
|
`<table class="table table-striped table-hover table-bordered">`,
|
||||||
|
`</table>`,
|
||||||
|
`<thead>`,
|
||||||
|
`</thead>`,
|
||||||
|
`<tbody>`,
|
||||||
|
`</tbody>`,
|
||||||
|
`<tfoot>`,
|
||||||
|
`</tfoot>`,
|
||||||
|
"Realização",
|
||||||
|
"Aquisição",
|
||||||
|
"Soma",
|
||||||
|
}
|
||||||
|
for _, s := range wantSubstrings {
|
||||||
|
if !strings.Contains(html, s) {
|
||||||
|
t.Errorf("RenderHTML output missing expected substring %q\nGot:\n%s", s, html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calling RenderHTML twice on the same instance must not duplicate rows.
|
||||||
|
html2 := pp.RenderHTML(aw)
|
||||||
|
if strings.Count(html2, "<tr") != strings.Count(html, "<tr") {
|
||||||
|
t.Errorf("expected second RenderHTML call to produce the same number of rows as the first\nFirst (%d <tr>):\n%s\nSecond (%d <tr>):\n%s",
|
||||||
|
strings.Count(html, "<tr"), html,
|
||||||
|
strings.Count(html2, "<tr"), html2)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,5 +42,9 @@
|
|||||||
"foreign_tax_paid": {
|
"foreign_tax_paid": {
|
||||||
"one": "Tax paid abroad",
|
"one": "Tax paid abroad",
|
||||||
"other": "Taxes paid abroad"
|
"other": "Taxes paid abroad"
|
||||||
|
},
|
||||||
|
"sum": {
|
||||||
|
"one": "Sum",
|
||||||
|
"other": "Sums"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,5 +42,9 @@
|
|||||||
"foreign_tax_paid": {
|
"foreign_tax_paid": {
|
||||||
"one": "Imposto pago no estrangeiro",
|
"one": "Imposto pago no estrangeiro",
|
||||||
"other": "Impostos pagos no estrangeiro"
|
"other": "Impostos pagos no estrangeiro"
|
||||||
|
},
|
||||||
|
"sum": {
|
||||||
|
"one": "Soma",
|
||||||
|
"other": "Somas"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user