styles_test.go 26.8 KB
Newer Older
1 2 3
package excelize

import (
4
	"fmt"
5
	"math"
6
	"path/filepath"
7
	"strings"
8 9 10 11 12
	"testing"

	"github.com/stretchr/testify/assert"
)

13 14 15
func TestStyleFill(t *testing.T) {
	cases := []struct {
		label      string
16
		format     *Style
17 18 19
		expectFill bool
	}{{
		label:      "no_fill",
20
		format:     &Style{Alignment: &Alignment{WrapText: true}},
21 22 23
		expectFill: false,
	}, {
		label:      "fill",
24
		format:     &Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"000000"}}},
25 26 27 28 29 30
		expectFill: true,
	}}

	for _, testCase := range cases {
		xl := NewFile()
		styleID, err := xl.NewStyle(testCase.format)
31
		assert.NoError(t, err)
32

33 34
		styles, err := xl.stylesReader()
		assert.NoError(t, err)
35 36
		style := styles.CellXfs.Xf[styleID]
		if testCase.expectFill {
37
			assert.NotEqual(t, *style.FillID, 0, testCase.label)
38
		} else {
39
			assert.Equal(t, *style.FillID, 0, testCase.label)
40 41
		}
	}
42
	f := NewFile()
43
	styleID1, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"000000"}}})
44
	assert.NoError(t, err)
45
	styleID2, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"000000"}}})
46 47 48
	assert.NoError(t, err)
	assert.Equal(t, styleID1, styleID2)
	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestStyleFill.xlsx")))
49 50
}

51 52 53
func TestSetConditionalFormat(t *testing.T) {
	cases := []struct {
		label  string
54
		format []ConditionalFormatOptions
55 56 57
		rules  []*xlsxCfRule
	}{{
		label: "3_color_scale",
58 59 60 61 62 63 64 65 66 67 68 69 70
		format: []ConditionalFormatOptions{{
			Type:     "3_color_scale",
			Criteria: "=",
			MinType:  "num",
			MidType:  "num",
			MaxType:  "num",
			MinValue: "-10",
			MidValue: "0",
			MaxValue: "10",
			MinColor: "ff0000",
			MidColor: "00ff00",
			MaxColor: "0000ff",
		}},
71 72 73 74 75 76
		rules: []*xlsxCfRule{{
			Priority: 1,
			Type:     "colorScale",
			ColorScale: &xlsxColorScale{
				Cfvo: []*xlsxCfvo{{
					Type: "num",
77
					Val:  "-10",
78 79
				}, {
					Type: "num",
80
					Val:  "0",
81 82
				}, {
					Type: "num",
83
					Val:  "10",
84 85 86 87 88 89 90 91 92 93 94 95
				}},
				Color: []*xlsxColor{{
					RGB: "FFFF0000",
				}, {
					RGB: "FF00FF00",
				}, {
					RGB: "FF0000FF",
				}},
			},
		}},
	}, {
		label: "3_color_scale default min/mid/max",
96 97 98 99 100 101 102 103 104 105
		format: []ConditionalFormatOptions{{
			Type:     "3_color_scale",
			Criteria: "=",
			MinType:  "num",
			MidType:  "num",
			MaxType:  "num",
			MinColor: "ff0000",
			MidColor: "00ff00",
			MaxColor: "0000ff",
		}},
106 107 108 109 110 111
		rules: []*xlsxCfRule{{
			Priority: 1,
			Type:     "colorScale",
			ColorScale: &xlsxColorScale{
				Cfvo: []*xlsxCfvo{{
					Type: "num",
112
					Val:  "0",
113 114
				}, {
					Type: "num",
115
					Val:  "50",
116 117
				}, {
					Type: "num",
118
					Val:  "0",
119 120 121 122 123 124 125 126 127 128 129 130
				}},
				Color: []*xlsxColor{{
					RGB: "FFFF0000",
				}, {
					RGB: "FF00FF00",
				}, {
					RGB: "FF0000FF",
				}},
			},
		}},
	}, {
		label: "2_color_scale default min/max",
131 132 133 134 135 136 137 138
		format: []ConditionalFormatOptions{{
			Type:     "2_color_scale",
			Criteria: "=",
			MinType:  "num",
			MaxType:  "num",
			MinColor: "ff0000",
			MaxColor: "0000ff",
		}},
139 140 141 142 143 144
		rules: []*xlsxCfRule{{
			Priority: 1,
			Type:     "colorScale",
			ColorScale: &xlsxColorScale{
				Cfvo: []*xlsxCfvo{{
					Type: "num",
145
					Val:  "0",
146 147
				}, {
					Type: "num",
148
					Val:  "0",
149 150 151 152 153 154 155 156 157 158 159
				}},
				Color: []*xlsxColor{{
					RGB: "FFFF0000",
				}, {
					RGB: "FF0000FF",
				}},
			},
		}},
	}}

	for _, testCase := range cases {
160
		f := NewFile()
161
		const sheet = "Sheet1"
xurime's avatar
xurime 已提交
162
		const rangeRef = "A1:A1"
xurime's avatar
xurime 已提交
163
		assert.NoError(t, f.SetConditionalFormat(sheet, rangeRef, testCase.format))
164
		ws, err := f.workSheetReader(sheet)
xurime's avatar
xurime 已提交
165
		assert.NoError(t, err)
166
		cf := ws.ConditionalFormatting
167 168
		assert.Len(t, cf, 1, testCase.label)
		assert.Len(t, cf[0].CfRule, 1, testCase.label)
xurime's avatar
xurime 已提交
169
		assert.Equal(t, rangeRef, cf[0].SQRef, testCase.label)
170 171
		assert.EqualValues(t, testCase.rules, cf[0].CfRule, testCase.label)
	}
xurime's avatar
xurime 已提交
172 173 174
	// Test creating a conditional format with a solid color data bar style
	f := NewFile()
	condFmts := []ConditionalFormatOptions{
175
		{Type: "data_bar", BarColor: "#A9D08E", BarSolid: true, Format: intPtr(0), Criteria: "=", MinType: "min", MaxType: "max"},
xurime's avatar
xurime 已提交
176 177 178 179
	}
	for _, ref := range []string{"A1:A2", "B1:B2"} {
		assert.NoError(t, f.SetConditionalFormat("Sheet1", ref, condFmts))
	}
180
	f = NewFile()
181 182 183 184
	// Test creating a conditional format without cell reference
	assert.Equal(t, ErrParameterRequired, f.SetConditionalFormat("Sheet1", "", nil))
	// Test creating a conditional format with invalid cell reference
	assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat("Sheet1", "A1:A2:A3", nil))
185
	// Test creating a conditional format with existing extension lists
xurime's avatar
xurime 已提交
186 187
	ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
	assert.True(t, ok)
188
	ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(`<ext uri="%s"><x14:slicerList /></ext><ext uri="%s"><x14:sparklineGroups /></ext>`, ExtURISlicerListX14, ExtURISparklineGroups)}
189 190 191 192 193 194
	assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A2", []ConditionalFormatOptions{{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarSolid: true}}))
	f = NewFile()
	// Test creating a conditional format with invalid extension list characters
	ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
	assert.True(t, ok)
	ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: "<ext><x14:conditionalFormattings></x14:conditionalFormatting></x14:conditionalFormattings></ext>"}
xurime's avatar
xurime 已提交
195
	assert.EqualError(t, f.SetConditionalFormat("Sheet1", "A1:A2", condFmts), "XML syntax error on line 1: element <conditionalFormattings> closed by </conditionalFormatting>")
196
	// Test creating a conditional format with invalid icon set style
197
	assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat("Sheet1", "A1:A2", []ConditionalFormatOptions{{Type: "icon_set", IconStyle: "unknown"}}))
198
	// Test unsupported conditional formatting rule types
199
	assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat("Sheet1", "A1", []ConditionalFormatOptions{{Type: "unsupported"}}))
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241

	t.Run("multi_conditional_formatting_rules_priority", func(t *testing.T) {
		f := NewFile()
		var condFmts []ConditionalFormatOptions
		for _, color := range []string{
			"#264B96", // Blue
			"#F9A73E", // Yellow
			"#006F3C", // Green
		} {
			condFmts = append(condFmts, ConditionalFormatOptions{
				Type:     "data_bar",
				Criteria: "=",
				MinType:  "num",
				MaxType:  "num",
				MinValue: "0",
				MaxValue: "5",
				BarColor: color,
				BarSolid: true,
			})
		}
		assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A5", condFmts))
		assert.NoError(t, f.SetConditionalFormat("Sheet1", "B1:B5", condFmts))
		for r := 1; r <= 20; r++ {
			cell, err := CoordinatesToCellName(1, r)
			assert.NoError(t, err)
			assert.NoError(t, f.SetCellValue("Sheet1", cell, r))
			cell, err = CoordinatesToCellName(2, r)
			assert.NoError(t, err)
			assert.NoError(t, f.SetCellValue("Sheet1", cell, r))
		}
		ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
		assert.True(t, ok)
		var priorities []int
		expected := []int{1, 2, 3, 4, 5, 6}
		for _, condFmt := range ws.(*xlsxWorksheet).ConditionalFormatting {
			for _, rule := range condFmt.CfRule {
				priorities = append(priorities, rule.Priority)
			}
		}
		assert.Equal(t, expected, priorities)
		assert.NoError(t, f.Close())
	})
242
}
243

244
func TestGetConditionalFormats(t *testing.T) {
245
	for _, format := range [][]ConditionalFormatOptions{
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
		{{Type: "cell", Format: intPtr(1), Criteria: "greater than", Value: "6"}},
		{{Type: "cell", Format: intPtr(1), Criteria: "between", MinValue: "6", MaxValue: "8"}},
		{{Type: "time_period", Format: intPtr(1), Criteria: "yesterday"}},
		{{Type: "time_period", Format: intPtr(1), Criteria: "today"}},
		{{Type: "time_period", Format: intPtr(1), Criteria: "tomorrow"}},
		{{Type: "time_period", Format: intPtr(1), Criteria: "last 7 days"}},
		{{Type: "time_period", Format: intPtr(1), Criteria: "last week"}},
		{{Type: "time_period", Format: intPtr(1), Criteria: "this week"}},
		{{Type: "time_period", Format: intPtr(1), Criteria: "continue week"}},
		{{Type: "time_period", Format: intPtr(1), Criteria: "last month"}},
		{{Type: "time_period", Format: intPtr(1), Criteria: "this month"}},
		{{Type: "time_period", Format: intPtr(1), Criteria: "continue month"}},
		{{Type: "text", Format: intPtr(1), Criteria: "containing", Value: "~!@#$%^&*()_+{}|:<>?\"';"}},
		{{Type: "text", Format: intPtr(1), Criteria: "not containing", Value: "text"}},
		{{Type: "text", Format: intPtr(1), Criteria: "begins with", Value: "prefix"}},
		{{Type: "text", Format: intPtr(1), Criteria: "ends with", Value: "suffix"}},
		{{Type: "top", Format: intPtr(1), Criteria: "=", Value: "6"}},
		{{Type: "bottom", Format: intPtr(1), Criteria: "=", Value: "6"}},
		{{Type: "average", AboveAverage: true, Format: intPtr(1), Criteria: "="}},
		{{Type: "duplicate", Format: intPtr(1), Criteria: "="}},
		{{Type: "unique", Format: intPtr(1), Criteria: "="}},
267 268
		{{Type: "3_color_scale", Criteria: "=", MinType: "num", MidType: "num", MaxType: "num", MinValue: "-10", MidValue: "50", MaxValue: "10", MinColor: "#FF0000", MidColor: "#00FF00", MaxColor: "#0000FF"}},
		{{Type: "2_color_scale", Criteria: "=", MinType: "num", MaxType: "num", MinColor: "#FF0000", MaxColor: "#0000FF"}},
269
		{{Type: "data_bar", Criteria: "=", MinType: "num", MaxType: "num", MinValue: "-10", MaxValue: "10", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarOnly: true, BarSolid: true, StopIfTrue: true}},
270
		{{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarDirection: "rightToLeft", BarOnly: true, BarSolid: true, StopIfTrue: true}},
271 272 273 274 275
		{{Type: "formula", Format: intPtr(1), Criteria: "="}},
		{{Type: "blanks", Format: intPtr(1)}},
		{{Type: "no_blanks", Format: intPtr(1)}},
		{{Type: "errors", Format: intPtr(1)}},
		{{Type: "no_errors", Format: intPtr(1)}},
276
		{{Type: "icon_set", IconStyle: "3Arrows", ReverseIcons: true, IconsOnly: true}},
277 278
	} {
		f := NewFile()
279
		err := f.SetConditionalFormat("Sheet1", "A2:A1,B:B,2:2", format)
280
		assert.NoError(t, err)
281
		opts, err := f.GetConditionalFormats("Sheet1")
282
		assert.NoError(t, err)
283
		assert.Equal(t, format, opts["A2:A1 B1:B1048576 A2:XFD2"])
284
	}
285
	// Test get multiple conditional formats
286
	f := NewFile()
287 288 289 290 291 292 293 294 295 296 297 298 299
	expected := []ConditionalFormatOptions{
		{Type: "data_bar", Criteria: "=", MinType: "num", MaxType: "num", MinValue: "-10", MaxValue: "10", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarOnly: true, BarSolid: true, StopIfTrue: true},
		{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarDirection: "rightToLeft", BarOnly: true, BarSolid: false, StopIfTrue: true},
	}
	err := f.SetConditionalFormat("Sheet1", "A1:A2", expected)
	assert.NoError(t, err)
	opts, err := f.GetConditionalFormats("Sheet1")
	assert.NoError(t, err)
	assert.Equal(t, expected, opts["A1:A2"])

	// Test get conditional formats on no exists worksheet
	f = NewFile()
	_, err = f.GetConditionalFormats("SheetN")
300
	assert.EqualError(t, err, "sheet SheetN does not exist")
301 302
	// Test get conditional formats with invalid sheet name
	_, err = f.GetConditionalFormats("Sheet:1")
303
	assert.Equal(t, ErrSheetNameInvalid, err)
304 305
}

306 307 308 309
func TestUnsetConditionalFormat(t *testing.T) {
	f := NewFile()
	assert.NoError(t, f.SetCellValue("Sheet1", "A1", 7))
	assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10"))
310
	format, err := f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}})
311
	assert.NoError(t, err)
312
	assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A10", []ConditionalFormatOptions{{Type: "cell", Criteria: ">", Format: &format, Value: "6"}}))
313
	assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10"))
314
	// Test unset conditional format on not exists worksheet
315
	assert.EqualError(t, f.UnsetConditionalFormat("SheetN", "A1:A10"), "sheet SheetN does not exist")
316
	// Test unset conditional format with invalid sheet name
317
	assert.Equal(t, ErrSheetNameInvalid, f.UnsetConditionalFormat("Sheet:1", "A1:A10"))
318
	// Save spreadsheet by the given path
319 320 321
	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnsetConditionalFormat.xlsx")))
}

322 323
func TestNewStyle(t *testing.T) {
	f := NewFile()
324 325
	for i := 0; i < 18; i++ {
		_, err := f.NewStyle(&Style{
326
			Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "4E71BE"}, Shading: i},
327 328 329 330
		})
		assert.NoError(t, err)
	}
	f = NewFile()
331
	styleID, err := f.NewStyle(&Style{Font: &Font{Bold: true, Italic: true, Family: "Times New Roman", Size: 36, Color: "777777"}})
xurime's avatar
xurime 已提交
332
	assert.NoError(t, err)
333 334
	styles, err := f.stylesReader()
	assert.NoError(t, err)
335
	fontID := styles.CellXfs.Xf[styleID].FontID
336
	font := styles.Fonts.Font[*fontID]
337
	assert.Contains(t, *font.Name.Val, "Times New Roman", "Stored font should contain font name")
338
	assert.Equal(t, 2, styles.CellXfs.Count, "Should have 2 styles")
339 340
	_, err = f.NewStyle(&Style{})
	assert.NoError(t, err)
341 342
	_, err = f.NewStyle(nil)
	assert.NoError(t, err)
343

344 345 346 347 348 349 350 351
	// Test gradient fills
	f = NewFile()
	styleID1, err := f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "4E71BE"}, Shading: 1, Pattern: 1}})
	assert.NoError(t, err)
	styleID2, err := f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"FF0000", "4E71BE"}, Shading: 1, Pattern: 1}})
	assert.NoError(t, err)
	assert.NotEqual(t, styleID1, styleID2)

xurime's avatar
xurime 已提交
352
	var exp string
353
	f = NewFile()
xurime's avatar
xurime 已提交
354
	_, err = f.NewStyle(&Style{CustomNumFmt: &exp})
355
	assert.Equal(t, ErrCustomNumFmt, err)
356
	_, err = f.NewStyle(&Style{Font: &Font{Family: strings.Repeat("s", MaxFontFamilyLength+1)}})
357
	assert.Equal(t, ErrFontLength, err)
358
	_, err = f.NewStyle(&Style{Font: &Font{Size: MaxFontSize + 1}})
359
	assert.Equal(t, ErrFontSize, err)
360

361
	// Test create numeric custom style
362
	numFmt := "####;####"
363 364
	f.Styles.NumFmts = nil
	styleID, err = f.NewStyle(&Style{
365
		CustomNumFmt: &numFmt,
366 367
	})
	assert.NoError(t, err)
368
	assert.Equal(t, 1, styleID)
369 370 371 372 373 374 375 376

	assert.NotNil(t, f.Styles)
	assert.NotNil(t, f.Styles.CellXfs)
	assert.NotNil(t, f.Styles.CellXfs.Xf)

	nf := f.Styles.CellXfs.Xf[styleID]
	assert.Equal(t, 164, *nf.NumFmtID)

377
	// Test create currency custom style
378 379 380 381 382
	f.Styles.NumFmts = nil
	styleID, err = f.NewStyle(&Style{
		NumFmt: 32, // must not be in currencyNumFmt
	})
	assert.NoError(t, err)
383
	assert.Equal(t, 2, styleID)
384 385 386 387 388 389 390

	assert.NotNil(t, f.Styles)
	assert.NotNil(t, f.Styles.CellXfs)
	assert.NotNil(t, f.Styles.CellXfs.Xf)

	nf = f.Styles.CellXfs.Xf[styleID]
	assert.Equal(t, 32, *nf.NumFmtID)
xurime's avatar
xurime 已提交
391

392
	// Test set build-in scientific number format
xurime's avatar
xurime 已提交
393 394 395 396 397 398 399
	styleID, err = f.NewStyle(&Style{NumFmt: 11})
	assert.NoError(t, err)
	assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "B1", styleID))
	assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]float64{1.23, 1.234}))
	rows, err := f.GetRows("Sheet1")
	assert.NoError(t, err)
	assert.Equal(t, [][]string{{"1.23E+00", "1.23E+00"}}, rows)
xurime's avatar
xurime 已提交
400 401

	f = NewFile()
402
	// Test currency number format
xurime's avatar
xurime 已提交
403 404 405 406 407 408 409 410 411 412 413 414 415 416
	customNumFmt := "[$$-409]#,##0.00"
	style1, err := f.NewStyle(&Style{CustomNumFmt: &customNumFmt})
	assert.NoError(t, err)
	style2, err := f.NewStyle(&Style{NumFmt: 165})
	assert.NoError(t, err)
	assert.Equal(t, style1, style2)

	style3, err := f.NewStyle(&Style{NumFmt: 166})
	assert.NoError(t, err)
	assert.Equal(t, 2, style3)

	f = NewFile()
	f.Styles.NumFmts = nil
	f.Styles.CellXfs.Xf = nil
417
	style4, err := f.NewStyle(&Style{NumFmt: 160})
xurime's avatar
xurime 已提交
418
	assert.NoError(t, err)
419
	assert.Equal(t, 0, style4)
xurime's avatar
xurime 已提交
420 421 422 423

	f = NewFile()
	f.Styles.NumFmts = nil
	f.Styles.CellXfs.Xf = nil
424
	style5, err := f.NewStyle(&Style{NumFmt: 160})
xurime's avatar
xurime 已提交
425
	assert.NoError(t, err)
426
	assert.Equal(t, 0, style5)
427

428
	// Test create style with unsupported charset style sheet
429 430 431 432
	f.Styles = nil
	f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
	_, err = f.NewStyle(&Style{NumFmt: 165})
	assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
433 434 435 436 437 438 439

	// Test create cell styles reach maximum
	f = NewFile()
	f.Styles.CellXfs.Xf = make([]xlsxXf, MaxCellStyles)
	f.Styles.CellXfs.Count = MaxCellStyles
	_, err = f.NewStyle(&Style{NumFmt: 0})
	assert.Equal(t, ErrCellStyles, err)
440 441
}

442
func TestConditionalStyle(t *testing.T) {
443
	f := NewFile()
444 445
	expected := &Style{Protection: &Protection{Hidden: true, Locked: true}}
	idx, err := f.NewConditionalStyle(expected)
446
	assert.NoError(t, err)
447 448 449
	style, err := f.GetConditionalStyle(idx)
	assert.NoError(t, err)
	assert.Equal(t, expected, style)
450 451 452 453
	_, err = f.NewConditionalStyle(&Style{DecimalPlaces: intPtr(4), NumFmt: 165, NegRed: true})
	assert.NoError(t, err)
	_, err = f.NewConditionalStyle(&Style{DecimalPlaces: intPtr(-1)})
	assert.NoError(t, err)
454 455 456 457
	expected = &Style{NumFmt: 1}
	idx, err = f.NewConditionalStyle(expected)
	assert.NoError(t, err)
	style, err = f.GetConditionalStyle(idx)
458
	assert.NoError(t, err)
459 460
	assert.Equal(t, expected.NumFmt, style.NumFmt)
	assert.Zero(t, *style.DecimalPlaces)
461 462 463 464 465 466 467 468
	_, err = f.NewConditionalStyle(&Style{NumFmt: 27})
	assert.NoError(t, err)
	numFmt := "general"
	_, err = f.NewConditionalStyle(&Style{CustomNumFmt: &numFmt})
	assert.NoError(t, err)
	numFmt1 := "0.00"
	_, err = f.NewConditionalStyle(&Style{CustomNumFmt: &numFmt1})
	assert.NoError(t, err)
469
	// Test create conditional style with unsupported charset style sheet
470 471
	f.Styles = nil
	f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
472
	_, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}})
473
	assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
474 475 476 477 478 479 480 481
	// Test get conditional style with invalid style index
	_, err = f.GetConditionalStyle(1)
	assert.Equal(t, newInvalidStyleID(1), err)
	// Test get conditional style with unsupported charset style sheet
	f.Styles = nil
	f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
	_, err = f.GetConditionalStyle(1)
	assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
482 483 484 485 486 487 488 489 490 491 492 493

	f = NewFile()
	// Test get conditional style with background color and empty pattern type
	idx, err = f.NewConditionalStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}})
	assert.NoError(t, err)
	f.Styles.Dxfs.Dxfs[0].Fill.PatternFill.PatternType = ""
	f.Styles.Dxfs.Dxfs[0].Fill.PatternFill.FgColor = nil
	f.Styles.Dxfs.Dxfs[0].Fill.PatternFill.BgColor = &xlsxColor{Theme: intPtr(6)}
	style, err = f.GetConditionalStyle(idx)
	assert.NoError(t, err)
	assert.Equal(t, "pattern", style.Fill.Type)
	assert.Equal(t, []string{"A5A5A5"}, style.Fill.Color)
494 495 496 497
}

func TestGetDefaultFont(t *testing.T) {
	f := NewFile()
498 499
	s, err := f.GetDefaultFont()
	assert.NoError(t, err)
500
	assert.Equal(t, s, "Calibri", "Default font should be Calibri")
501
	// Test get default font with unsupported charset style sheet
502 503 504 505
	f.Styles = nil
	f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
	_, err = f.GetDefaultFont()
	assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
506 507 508 509
}

func TestSetDefaultFont(t *testing.T) {
	f := NewFile()
510 511 512 513 514
	assert.NoError(t, f.SetDefaultFont("Arial"))
	styles, err := f.stylesReader()
	assert.NoError(t, err)
	s, err := f.GetDefaultFont()
	assert.NoError(t, err)
515
	assert.Equal(t, s, "Arial", "Default font should change to Arial")
516
	assert.Equal(t, *styles.CellStyles.CellStyle[0].CustomBuiltIn, true)
517
	// Test set default font with unsupported charset style sheet
518 519 520
	f.Styles = nil
	f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
	assert.EqualError(t, f.SetDefaultFont("Arial"), "XML syntax error on line 1: invalid UTF-8")
521
}
522 523 524

func TestStylesReader(t *testing.T) {
	f := NewFile()
525
	// Test read styles with unsupported charset
526
	f.Styles = nil
527
	f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
528 529 530
	styles, err := f.stylesReader()
	assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
	assert.EqualValues(t, new(xlsxStyleSheet), styles)
531 532 533 534
}

func TestThemeReader(t *testing.T) {
	f := NewFile()
535
	// Test read theme with unsupported charset
536
	f.Pkg.Store(defaultXMLPathTheme, MacintoshCyrillicCharset)
537 538
	theme, err := f.themeReader()
	assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
539
	assert.EqualValues(t, &decodeTheme{}, theme)
540 541 542 543
}

func TestSetCellStyle(t *testing.T) {
	f := NewFile()
544
	// Test set cell style on not exists worksheet
545
	assert.EqualError(t, f.SetCellStyle("SheetN", "A1", "A2", 1), "sheet SheetN does not exist")
546
	// Test set cell style with invalid style ID
547
	assert.Equal(t, newInvalidStyleID(-1), f.SetCellStyle("Sheet1", "A1", "A2", -1))
548
	// Test set cell style with not exists style ID
549
	assert.Equal(t, newInvalidStyleID(10), f.SetCellStyle("Sheet1", "A1", "A2", 10))
550
	// Test set cell style with unsupported charset style sheet
551 552 553
	f.Styles = nil
	f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
	assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", 1), "XML syntax error on line 1: invalid UTF-8")
554
}
555 556

func TestGetStyleID(t *testing.T) {
557 558 559 560
	f := NewFile()
	styleID, err := f.getStyleID(&xlsxStyleSheet{}, nil)
	assert.NoError(t, err)
	assert.Equal(t, -1, styleID)
561
	// Test get style ID with unsupported charset style sheet
562 563 564 565 566 567 568 569 570
	f.Styles = nil
	f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
	_, err = f.getStyleID(&xlsxStyleSheet{
		CellXfs: &xlsxCellXfs{},
		Fonts: &xlsxFonts{
			Font: []*xlsxFont{{}},
		},
	}, &Style{NumFmt: 0, Font: &Font{}})
	assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
571 572 573
}

func TestGetFillID(t *testing.T) {
574 575 576
	styles, err := NewFile().stylesReader()
	assert.NoError(t, err)
	assert.Equal(t, -1, getFillID(styles, &Style{Fill: Fill{Type: "unknown"}}))
577
}
578

579 580 581 582 583 584 585 586 587 588 589
func TestThemeColor(t *testing.T) {
	for _, clr := range [][]string{
		{"FF000000", ThemeColor("000000", -0.1)},
		{"FF000000", ThemeColor("000000", 0)},
		{"FF33FF33", ThemeColor("00FF00", 0.2)},
		{"FFFFFFFF", ThemeColor("000000", 1)},
		{"FFFFFFFF", ThemeColor(strings.Repeat(string(rune(math.MaxUint8+1)), 6), 1)},
		{"FFFFFFFF", ThemeColor(strings.Repeat(string(rune(-1)), 6), 1)},
	} {
		assert.Equal(t, clr[0], clr[1])
	}
590
}
591 592 593 594

func TestGetNumFmtID(t *testing.T) {
	f := NewFile()

595
	fs1, err := parseFormatStyleSet(&Style{Protection: &Protection{Hidden: false, Locked: false}, NumFmt: 10})
596 597 598
	assert.NoError(t, err)
	id1 := getNumFmtID(&xlsxStyleSheet{}, fs1)

599
	fs2, err := parseFormatStyleSet(&Style{Protection: &Protection{Hidden: false, Locked: false}, NumFmt: 0})
600 601 602 603 604
	assert.NoError(t, err)
	id2 := getNumFmtID(&xlsxStyleSheet{}, fs2)

	assert.NotEqual(t, id1, id2)
	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestStyleNumFmt.xlsx")))
xurime's avatar
xurime 已提交
605
}
606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657

func TestGetThemeColor(t *testing.T) {
	assert.Empty(t, (&File{}).getThemeColor(&xlsxColor{}))
	f := NewFile()
	assert.Empty(t, f.getThemeColor(nil))
	var theme int
	assert.Equal(t, "FFFFFF", f.getThemeColor(&xlsxColor{Theme: &theme}))
	assert.Equal(t, "FFFFFF", f.getThemeColor(&xlsxColor{RGB: "FFFFFF"}))
	assert.Equal(t, "FF8080", f.getThemeColor(&xlsxColor{Indexed: 2, Tint: 0.5}))
	assert.Empty(t, f.getThemeColor(&xlsxColor{Indexed: len(IndexedColorMapping), Tint: 0.5}))
}

func TestGetStyle(t *testing.T) {
	f := NewFile()
	expected := &Style{
		Border: []Border{
			{Type: "left", Color: "0000FF", Style: 3},
			{Type: "right", Color: "FF0000", Style: 6},
			{Type: "top", Color: "00FF00", Style: 4},
			{Type: "bottom", Color: "FFFF00", Style: 5},
			{Type: "diagonalUp", Color: "A020F0", Style: 7},
			{Type: "diagonalDown", Color: "A020F0", Style: 7},
		},
		Fill: Fill{Type: "gradient", Shading: 16, Color: []string{"0000FF", "00FF00"}},
		Font: &Font{
			Bold: true, Italic: true, Underline: "single", Family: "Arial",
			Size: 8.5, Strike: true, Color: "777777", ColorIndexed: 1, ColorTint: 0.1,
		},
		Alignment: &Alignment{
			Horizontal:      "center",
			Indent:          1,
			JustifyLastLine: true,
			ReadingOrder:    1,
			RelativeIndent:  1,
			ShrinkToFit:     true,
			TextRotation:    180,
			Vertical:        "center",
			WrapText:        true,
		},
		Protection: &Protection{Hidden: true, Locked: true},
		NumFmt:     49,
	}
	styleID, err := f.NewStyle(expected)
	assert.NoError(t, err)
	style, err := f.GetStyle(styleID)
	assert.NoError(t, err)
	assert.Equal(t, expected.Border, style.Border)
	assert.Equal(t, expected.Fill, style.Fill)
	assert.Equal(t, expected.Font, style.Font)
	assert.Equal(t, expected.Alignment, style.Alignment)
	assert.Equal(t, expected.Protection, style.Protection)
	assert.Equal(t, expected.NumFmt, style.NumFmt)
658
	assert.Nil(t, style.DecimalPlaces)
659 660 661 662 663 664 665 666 667

	expected = &Style{
		Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"0000FF"}},
	}
	styleID, err = f.NewStyle(expected)
	assert.NoError(t, err)
	style, err = f.GetStyle(styleID)
	assert.NoError(t, err)
	assert.Equal(t, expected.Fill, style.Fill)
668 669 670 671 672 673 674 675 676
	assert.Nil(t, style.DecimalPlaces)

	expected = &Style{NumFmt: 2}
	styleID, err = f.NewStyle(expected)
	assert.NoError(t, err)
	style, err = f.GetStyle(styleID)
	assert.NoError(t, err)
	assert.Equal(t, expected.NumFmt, style.NumFmt)
	assert.Equal(t, 2, *style.DecimalPlaces)
677 678 679 680 681 682 683

	expected = &Style{NumFmt: 27}
	styleID, err = f.NewStyle(expected)
	assert.NoError(t, err)
	style, err = f.GetStyle(styleID)
	assert.NoError(t, err)
	assert.Equal(t, expected.NumFmt, style.NumFmt)
684
	assert.Nil(t, style.DecimalPlaces)
685 686 687 688 689 690 691

	expected = &Style{NumFmt: 165}
	styleID, err = f.NewStyle(expected)
	assert.NoError(t, err)
	style, err = f.GetStyle(styleID)
	assert.NoError(t, err)
	assert.Equal(t, expected.NumFmt, style.NumFmt)
692
	assert.Equal(t, 2, *style.DecimalPlaces)
693

694 695
	decimal := 4
	expected = &Style{NumFmt: 165, DecimalPlaces: &decimal, NegRed: true}
696 697 698 699
	styleID, err = f.NewStyle(expected)
	assert.NoError(t, err)
	style, err = f.GetStyle(styleID)
	assert.NoError(t, err)
700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
	assert.Equal(t, 0, style.NumFmt)
	assert.Equal(t, *expected.DecimalPlaces, *style.DecimalPlaces)
	assert.Equal(t, "[$$-409]#,##0.0000;[Red][$$-409]#,##0.0000", *style.CustomNumFmt)

	for _, val := range [][]interface{}{
		{"$#,##0", 0},
		{"$#,##0.0", 1},
		{"_($* #,##0_);_($* (#,##0);_($* \"-\"_);_(@_)", 0},
		{"_($* #,##000_);_($* (#,##000);_($* \"-\"_);_(@_)", 0},
		{"_($* #,##0.0000_);_($* (#,##0.0000);_($* \"-\"????_);_(@_)", 4},
	} {
		numFmtCode := val[0].(string)
		expected = &Style{CustomNumFmt: &numFmtCode}
		styleID, err = f.NewStyle(expected)
		assert.NoError(t, err)
		style, err = f.GetStyle(styleID)
		assert.NoError(t, err)
		assert.Equal(t, val[1].(int), *style.DecimalPlaces, numFmtCode)
	}

	for _, val := range []string{
		";$#,##0",
		";$#,##0;",
		";$#,##0.0",
		";$#,##0.0;",
		"$#,##0;0.0",
		"_($* #,##0_);;_($* \"-\"_);_(@_)",
		"_($* #,##0.0_);_($* (#,##0.00);_($* \"-\"_);_(@_)",
	} {
		expected = &Style{CustomNumFmt: &val}
		styleID, err = f.NewStyle(expected)
		assert.NoError(t, err)
		style, err = f.GetStyle(styleID)
		assert.NoError(t, err)
		assert.Nil(t, style.DecimalPlaces)
	}
736 737 738

	// Test get style with custom color index
	f.Styles.Colors = &xlsxStyleColors{
739
		IndexedColors: &xlsxIndexedColors{
740 741 742 743 744
			RgbColor: []xlsxColor{{RGB: "FF012345"}},
		},
	}
	assert.Equal(t, "012345", f.getThemeColor(&xlsxColor{Indexed: 0}))

xurime's avatar
xurime 已提交
745 746 747 748 749 750
	f.Styles.Fonts.Font[0].U = &attrValString{}
	f.Styles.CellXfs.Xf[0].FontID = intPtr(0)
	style, err = f.GetStyle(styleID)
	assert.NoError(t, err)
	assert.Equal(t, "single", style.Font.Underline)

751 752 753 754 755 756 757 758 759 760 761
	// Test get style with invalid style index
	style, err = f.GetStyle(-1)
	assert.Nil(t, style)
	assert.Equal(t, err, newInvalidStyleID(-1))
	// Test get style with unsupported charset style sheet
	f.Styles = nil
	f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
	style, err = f.GetStyle(1)
	assert.Nil(t, style)
	assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}