howl.ui.theme
assigning directly to .current raises an error
File.with_tmpfile (file) ->
theme.register 'foo', file
assert.has_errors -> config.current = 'foo'
register(name, file)
adds name to .all
file = File 'test'
theme.register 'test', file
assert.equal theme.all.test, file
raises an error if name is omitted
status, msg = pcall theme.register, nil, File 'foo'
assert.is_false(status)
assert.match msg, 'name'
raises an error if file is omitted
status, msg = pcall theme.register, 'test'
assert.is_false(status)
assert.match msg, 'file'
unregister(name)
removes the theme from all
file = File 'tmp'
theme.register 'tmp', file
theme.unregister 'tmp'
assert.is_nil theme.all.tmp
assigning a new theme to config.theme
logs an error if there's an error loading the theme
File.with_tmpfile (file) ->
file.contents = "cantload!"
theme.register 'error', file
config.theme = 'error'
assert.match log.last_error.message, 'Theme error'
assigns the loaded theme to .current and sets .name
File.with_tmpfile (file) ->
theme.register 'foo', file
config.theme = 'foo'
assert.equal 'foo', theme.current.name
(switching themes)
drops the styles, flairs and gutter color set only by the previous theme
File.with_tmpfile (first) ->
File.with_tmpfile (second) ->
first.contents = '
style.first-only { color: red; }
flair.first-only { shape: pipe; border-color: red; }
.gutter { color: red; }
'
theme.register 'first', first
theme.register 'second', second
config.theme = 'first'
assert.is_not_nil style.first_only
assert.is_not_nil flair.get 'first_only'
assert.equal 'red', aullar_config.gutter_color
config.theme = 'second'
assert.is_nil style.first_only
assert.is_nil flair.get 'first_only'
assert.equal aullar_config.definition_for('gutter_color').default, aullar_config.gutter_color
(style parsing)
load_css = (css) ->
File.with_tmpfile (file) ->
file.contents = css
theme.register 'styles', file
config.theme = 'styles'
theme.current.styles
assert.equal 'red', load_css('style.foo { color: red; }').foo.color
underscores the style names
assert.equal 'red', load_css('style.foo-bar { color: red; }').foo_bar.color
accepts style names given with underscores
assert.equal 'red', load_css('style.foo_bar { color: red; }').foo_bar.color
accepts a last declaration without a semicolon
assert.equal 'red', load_css('style.foo { color: red }').foo.color
assert.equal 'red', load_css([[
style.foo {
/* what: never mind me */
color: red;
}
]]).foo.color
translates font-style: italic as font.italic
assert.same {
foo: {
font: italic: true
}
}, load_css 'style.foo { font-style: italic; }'
translates font-weight: bold as font.bold
assert.same {
foo: {
font: bold: true
}
}, load_css 'style.foo { font-weight: bold; }'
translates font-size font.size
assert.same {
foo: {
font: size: 'large'
}
}, load_css 'style.foo { font-size: large; }'
translates numeric font sizes, in points, as numbers
assert.equal 14, load_css('style.foo { font-size: 14; }').foo.font.size
assert.equal 14, load_css('style.foo { font-size: 14pt; }').foo.font.size
translates font-family as font.family
assert.same {
foo: {
font: family: 'monospace'
}
}, load_css 'style.foo { font-family: monospace; }'
strips quotes and spaces from a list of font families
assert.equal 'Latin Modern,Purisa', load_css(
[[style.foo { font-family: "Latin Modern", 'Purisa'; }]]
).foo.font.family
translates color as color
assert.equals 'green', load_css('style.foo { color: green; }').foo.color
converts colors that Pango does not understand to hex
assert.equals '#ff0000', load_css('style.foo { color: rgb(255, 0, 0); }').foo.color
translates background-color as background
assert.equals 'green', load_css(
'style.foo { background-color: green; }'
).foo.background
allows alpha values for background-color
assert.equals '#dddddd70', load_css(
'style.foo { background-color: #dddddd70; }'
).foo.background
implements the Gtk alpha function
assert.equals '#00ff0080', load_css(
'style.foo { background-color: alpha(#00ff00, 0.5); }'
).foo.background
handles color names for the Gtk alpha function
assert.equals '#00000080', load_css(
'style.foo { background-color: alpha(black, 0.5); }'
).foo.background
translates text-decoration underline as underline
assert.is_true load_css(
'style.foo { text-decoration: underline; }'
).foo.underline
translates text-decoration line-through as strike_through
assert.is_true load_css(
'style.foo { text-decoration: line-through; }'
).foo.strike_through
(flair parsing)
load_css = (css) ->
File.with_tmpfile (file) ->
file.contents = css
theme.register 'flairs', file
config.theme = 'flairs'
theme.current.flairs
assert.same {
type: 'pipe',
foreground: 'red'
}, load_css('flair.foo { shape: pipe; border-color: red; }').foo
underscores the style names
assert.equal 'red', load_css(
'flair.foo-bar { shape: pipe; border-color: red; }'
).foo_bar.foreground
translates width as line_width
assert.equal 2, load_css(
'flair.foo { shape: pipe; border-color: red; width: 2; }'
).foo.line_width
translates border-style as line_type
assert.equal 'dotted', load_css(
'flair.foo { shape: pipe; border-color: red; border-style: dotted; }'
).foo.line_type
translates minimum-width as min_width
assert.equal 2, load_css(
'flair.foo { shape: pipe; minimum-width: 2; }'
).foo.min_width
translates color as text_color
assert.equal 'red', load_css(
'flair.foo { shape: pipe; color: red; }'
).foo.text_color
translates background-color as background
assert.equal 'red', load_css(
'flair.foo { shape: pipe; background-color: red; }'
).foo.background
translates border-radius as corner_radius
assert.equal 2, load_css(
'flair.foo { shape: rounded-rectangle; border-radius: 2; }'
).foo.corner_radius
translates height as a number, or "text"
assert.equal 10, load_css('flair.foo { shape: pipe; height: 10px; }').foo.height
assert.equal 'text', load_css('flair.foo { shape: pipe; height: text; }').foo.height
(mistakes)
load_css = (css) ->
File.with_tmpfile (file) ->
file.contents = css
theme.register 'mistakes', file
config.theme = 'mistakes'
theme.current
assert_logged = (pattern) ->
assert.includes log.last_error.message, pattern
logs unknown properties and applies the rest of the rule
t = load_css 'style.foo { colour: red; color: blue; }'
assert_logged "Theme error: style.foo: unknown property 'colour'"
assert.equal 'blue', t.styles.foo.color
logs declarations that are not "property: value"
load_css 'style.foo { font: bold: true }'
assert_logged "style.foo: invalid declaration 'font: bold: true'"
logs invalid values
load_css 'style.foo { font-weight: 700; }'
assert_logged "style.foo: invalid font-weight '700'"
load_css 'flair.foo { shape: pipe; border-color: nocolor; }'
assert_logged "flair.foo: invalid color 'nocolor'"
logs transparent text colors, which Pango can not draw
load_css 'style.foo { color: alpha(red, 0.5); }'
assert_logged "style.foo: text colors can't be transparent"
logs and drops flairs without a valid shape
t = load_css 'flair.foo { border-color: red; } flair.bar { shape: blob; }'
assert.is_nil t.flairs.foo
assert.is_nil t.flairs.bar
assert_logged 'no valid shape, ignoring it'
logs style and flair selectors with more than a single name
load_css 'window { color: red; } style.foo, style.bar { color: red; }'
assert_logged "unsupported selector 'style.foo, style.bar'"
(custom value extractions)
load_css = (css) ->
File.with_tmpfile (file) ->
file.contents = css
theme.register 'custom', file
config.theme = 'custom'
theme.current.custom
assert.equal 'red', load_css(
'.gutter { color: red; }'
).gutter_color
(CSS var declarations)
load_css = (css) ->
File.with_tmpfile (file) ->
file.contents = css
theme.register 'vars', file
config.theme = 'vars'
theme.current
parses vars from :root and allows use of them
assert.equal 'green', load_css(
'
:root {
}
style.foo { color: var(--my-var); }
'
).styles.foo.color
allows vars to reference other vars
assert.equal 'green', load_css(
'
:root {
}
style.foo { color: var(--sub); }
'
).styles.foo.color