UNPKG

postcss-grid-kiss

Version:
905 lines (795 loc) 25.2 kB
const postcss = require('postcss'), gridkiss = require('./lib/main'), test = require('ava'); const { remaining, sum } = require("./lib/calc-utils"); const { parseDimension } = require("./lib/dimension"); const caniuse = require("./lib/caniuse"); async function process(input, options) { return postcss(gridkiss(options)).process(input, { from: undefined }).then(res => { const output = {}; res.root.walkRules((rule) => { if (rule.parent === res.root) { output[rule.selector] = {}; rule.walkDecls((decl) => { output[rule.selector][decl.prop] = decl.value; }) } }); res.root.walkAtRules((atrule) => { output[atrule.name] = { params: atrule.params }; atrule.walkRules((rule) => { output[atrule.name][rule.selector] = {}; rule.walkDecls((decl) => { output[atrule.name][rule.selector][decl.prop] = decl.value; }) }); }); return output; }).catch(err => console.error(err)); } test("parsing dimensions", t => { t.is(parseDimension("1"), "1fr") t.is(parseDimension("1px"), "1px") t.is(parseDimension("0"), "0fr") t.is(parseDimension("10"), "10fr") t.is(parseDimension("12.34em"), "12.34em") t.is(parseDimension("12.34em - 56.78vmin"), "minmax(12.34em, 56.78vmin)") t.is(parseDimension("> 3rem"), "minmax(3rem, auto)") t.is(parseDimension("< 10"), "minmax(auto, 10fr)") t.is(parseDimension("50%"), "50%") t.is(parseDimension("12.5%"), "12.5%") t.is(parseDimension("50% grid"), "50%") t.is(parseDimension("5.55% free"), "5.55fr") t.is(parseDimension("50% view", "horizontal"), "50vw") t.is(parseDimension("50% view", "vertical"), "50vh") t.is(parseDimension("calc(20% + 10px)"), "calc(20% + 10px)") t.is(parseDimension("calc(20% + 10%)"), "calc(20% + 10%)") t.is(parseDimension("min"), "min-content") t.is(parseDimension("max"), "max-content") t.is(parseDimension("var(--test)"), "var(--test)") t.is(parseDimension("$customVar", "vertical", { dimensionParser(dim) { if (/\$[\w-]+/.test(dim)) return dim // custom var syntax, leave untouched for next pcss plugin in the chain return null; // unrecognized syntax } }), "$customVar"); t.is(parseDimension("v(my-cool-length)", "horizontal", { dimensionParser(dim) { const CUSTOM_VAR_SYNTAX = /v\(([^)]+)\)/ const varMatch = dim.match(CUSTOM_VAR_SYNTAX) if (varMatch != null) { return `var(--${varMatch[1]})` } return null; // unrecognized syntax } }), "var(--my-cool-length)"); }) test("calc utils", t => { t.is(sum("120px", "1em", "100px", "2em"), "calc(220px + 3em)"); t.is(remaining('120px', '1em', '2em', '100px'), "calc(100% - 220px - 3em)"); }) test("browser support", t => { t.is(caniuse.cssGrid("IE 11"), false) t.is(caniuse.cssGrid("last 2 Chrome versions"), true) t.is(caniuse.cssSupportsApi("Firefox > 20"), false) t.is(caniuse.cssSupportsApi("Firefox > 40"), true) }) test('display grid', async t => { let output = await process( `div { grid-kiss: "+------+" "| test |" "+------+" }`, { fallback: true }); t.is(output["div"]["display"], "grid"); }); test('align-content stretch', async t => { let output = await process( `div { grid-kiss: "+------+" "| test |" "+------+" }`); t.is(output["div"]["align-content"], "stretch"); }) test('align-content start', async t => { let output = await process( `div { grid-kiss: "+------+" "| test |" "+------+" " " }`); t.is(output["div"]["align-content"], "start"); }) test('align-content end', async t => { let output = await process( `div { grid-kiss: " " "+------+" "| test |" "+------+" }`); t.is(output["div"]["align-content"], "end"); }); test('align-content center', async t => { let output = await process( `div { grid-kiss: " " "+------+" "| test |" "+------+" " " }`); t.is(output["div"]["align-content"], "center"); }); test('align-content space-between', async t => { let output = await process( `div { grid-kiss: "+------+" "| foo |" "+------+" " " "+------+" "| bar |" "+------+" }`); t.is(output["div"]["align-content"], "space-between"); }) test('align-content space-evenly', async t => { let output = await process( `div { grid-kiss: " " "+------+" "| foo |" "+------+" " " "+------+" "| bar |" "+------+" " " }`); t.is(output["div"]["align-content"], "space-evenly"); }) test('align-content space-around', async t => { let output = await process( `div { grid-kiss: " " "+------+" "| foo |" "+------+" " " " " "+------+" "| bar |" "+------+" " " }`); t.is(output["div"]["align-content"], "space-around"); }) test('justify-content start', async t => { let output = await process( `div { grid-kiss: "+------++------+ " "| foo || bar | " "+------++------+ " }`); t.is(output["div"]["justify-content"], "start"); }); test('justify-content end', async t => { let output = await process( `div { grid-kiss: " +-----++-----+" " | foo || bar |" " +-----++-----+" }`) t.is(output["div"]["justify-content"], "end"); }); test('justify-content center', async t => { let output = await process( `div { grid-kiss: " +------++------+ " " | foo || bar | " " +------++------+ " }`) t.is(output["div"]["justify-content"], "center"); }); test('justify-content space-between', async t => { let output = await process( `div { grid-kiss: "+------+ +------+" "| foo | | bar |" "+------+ +------+" }`) t.is(output["div"]["justify-content"], "space-between"); }); test('justify-content space-evenly', async t => { let output = await process( `div { grid-kiss: " +------+ +------+ " " | foo | | bar | " " +------+ +------+ " }`) t.is(output["div"]["justify-content"], "space-evenly"); }); test('justify-content space-around', async t => { let output = await process( `div { grid-kiss: " +------+ +------+ " " | foo | | bar | " " +------+ +------+ " }`) t.is(output["div"]["justify-content"], "space-around"); }); test('grid template rows', async t => { let output = await process( `div { grid-kiss: " +------+ +------+ " " | foo | | bar | 40px " " +------+ +------+ " " +------+ +------+ " " | bar | | foo | 15% " " +------+ +------+ " }`, { optimize: false }) t.is(output["div"]["grid-template-rows"], "40px 15%"); output = await process( `div { grid-kiss: "+------+ +------+ " "| | | bar | " "| | +------+ " "| baz | " "| | +------+ " "| | | foo | 50% free" "+------+ +------+ " " " "+------+ +------+ " "| bar | | foo | > 20fr" "+------+ +------+ " }`, { optimize: false }) t.is(output["div"]["grid-template-rows"], "1fr 50fr minmax(20fr, auto)"); output = await process( `div { grid-kiss: '+------+ +------+ - ' '| | | ^ | <10% ' '| | | bar >| ' '| v | +------+ - ' '| baz | ' '| ^ | +------+ - ' '| | | ^ | 50% free' '+------+ | | - ' ' | foo | ' '+------+ | | - ' '| < qux| | v | > 20fr' '| v | | | ' '+------+ +------+ - ' }`, { optimize: false }); t.is(output["div"]["grid-template-rows"], "minmax(auto, 10%) 50fr minmax(20fr, auto)"); }); test('grid template columns', async t => { let output = await process( `div { grid-kiss: " +-50px-+ +------+ " " | foo | | bar | 40px " " +------+ +------+ " " +------+ +-25.5%+ " " | bar | | foo | 15% " " +------+ +------+ " }`, { optimize: false }) t.is(output["div"]["grid-template-columns"], "50px 25.5%"); output = await process( `div { grid-kiss: "+----------------+ +-----+" "| foobar | | baz |" "+----------------+ +100px+" "+-------+ +-20% -+ +-----+" "| bar | | foo | | qux |" "+ > 4em + +------+ +-----+" }`, { optimize: false }) t.is(output["div"]["grid-template-columns"], "minmax(4em, auto) 20% 100px"); output = await process( `div { grid-kiss: "+-------------+ +-----+" "| .bigzone | | |" "+-------------+ +-----+" "+-----+ +-------------+" "| | | .bigzone2 |" "+-----+ +-------------+" "|20% | |66.7%| |13.3%|" }`, { optimize: false }) t.is(output["div"]["grid-template-columns"], "20% 66.7% 13.3%"); output = await process( `div { grid-kiss: "+-------------+ +-20%-+" "| .bigzone | | |" "+-------------+ +-----+" "+-----+ +-------------+" "| | | .bigzone2 |" "+-20%-+ +-------------+" " | 60% | " }`, { optimize: false }) t.is(output["div"]["grid-template-columns"], "20% 60% 20%"); }); test('grid template areas', async t => { let output = await process( `div { grid-kiss: "+------+ +------+ " "| | | bar | " "| | +------+ " "| baz | " "| | +------+ " "| | | foo | 50% free" "+------+ +------+ " " " "+------+ +------+ " "| bar | | foo | > 20fr" "+------+ +------+ " }`, { optimize: false }) t.is(output["div"]["grid-template-areas"], `\n\t\t"baz bar"\n\t\t"baz foo"\n\t\t"bar foo"`); t.is(output["div > bar"]["grid-area"], "bar"); t.is(output["div > baz"]["grid-area"], "baz"); t.is(output["div > foo"]["grid-area"], "foo"); output = await process( `div { grid-kiss: "+----------------+ +-----+" "|foo#bar.baz[qux]| | baz |" "+----------------+ +100px+" "+------+ +-20% -+ +-----+" "| .bar | | #foo | | qux |" "+ > 4em+ +------+ +-----+" }`, { optimize: false }) t.is(output["div"]["grid-template-areas"], `\n\t\t"foo_bar_baz_qux foo_bar_baz_qux baz"\n\t\t"bar foo qux"`); t.is(output["div > .bar"]["grid-area"], "bar"); t.is(output["div > baz"]["grid-area"], "baz"); t.is(output["div > #foo"]["grid-area"], "foo"); t.is(output["div > qux"]["grid-area"], "qux"); t.is(output["div > foo#bar.baz[qux]"]["grid-area"], "foo_bar_baz_qux"); }); test('zone justify-self', async t => { let output = await process( `div { grid-kiss: "+-----------+ +------------+ " "| | | ^ | " "| | | bar -> | " "| v | +------------+ " "| baz <-- | " "| ^ | +------------+ " "| | | | " "+-----------+ | | " " | → foo ← | " "+-----------+ | | " "| < qux > | | | " "| v | | | " "+-----------+ +------------+ " }`); t.is(output["div > baz"]["justify-self"], "start"); t.is(output["div > bar"]["justify-self"], "end"); t.is(output["div > qux"]["justify-self"], "stretch"); t.is(output["div > foo"]["justify-self"], "center"); }); test('zone align-self', async t => { let output = await process( `div { grid-kiss: "+------+ +------+ " "| | | ^ | " "| | | bar >| " "| v | +------+ " "| baz | " "| ^ | +------+ " "| | | ^ | 50% free" "+------+ | | " " | foo | " "+------+ | | " "| < qux| | v | > 20fr" "| v | | | " "+------+ +------+ " }`); t.is(output["div > baz"]["align-self"], "center"); t.is(output["div > bar"]["align-self"], "start"); t.is(output["div > qux"]["align-self"], "end"); t.is(output["div > foo"]["align-self"], "stretch"); }); test('gaps', async t => { let output = await process( `div { grid-kiss: "+--------------------+ +-----+" "| .bigzone | |.foo |" "+--------------------+ +-----+" "+-----+ +--------------------+" "|.bar | | .bigzone2 |" "+-----+ +--------------------+" "| 20% | 10px | min | auto | 10% |" }`, { optimize: false }); t.is(output["div"]["grid-template-columns"], "20% 10px min-content 1fr 10%"); output = await process( `div { grid-kiss: "+------+ +------+ " "| | | ^ | " "| | | bar >| 1 " "| v | +------+ " "| baz | 2 " "| ^ | +------+ " "| | | ^ | 3 " "+------+ | | " " | foo | 4 " "+------+ | | " "| < qux| | v | 5 " "| v | | | " "+------+ +------+ " }`, { optimize: false }); t.is(output["div"]["grid-template-rows"], "1fr 2fr 3fr 4fr 5fr"); output = await process( `body { grid-kiss: "+-----+ +-----+ +-----+100px " "| .nw | | .n | | .ne | " "+-----+ +-----+ +-----+ ----" " 50px" "+-----+ +-----+ +-----+ ----" "| .w | | | | .e | 100px" "+-----+ +-----+ +-----+ " " 50px " "+-----+ +-----+ +-----+ ----" "| .sw | | .s | | .se | " "+-----+ +-----+ +-----+ 100px" "| 100px50px |100px| 50px100px " }`, { optimize: false }); t.is(output["body"]["grid-template-columns"], "100px 50px 100px 50px 100px"); t.is(output["body"]["grid-template-rows"], "100px 50px 100px 50px 100px"); t.is(output["body"]["grid-template-areas"], `\n\t\t"nw ... n ... ne "\n\t\t"... ... ... ... ..."\n\t\t"w ... ... ... e "` + `\n\t\t"... ... ... ... ..."\n\t\t"sw ... s ... se "`); }) test('other ascii formats: simple segments', async t => { let output = await process( `div { grid-kiss: "┌──────┐ ┌──────┐ " "│ │ │ bar →│ 200px" "│ ↓ │ └──────┘ " "│ baz │ - " "│ ↑ │ ┌──────┐ " "│ │ │ ↑ │ max" "└──────┘ │ │ " " │ foo │ - " "┌──────┐ │ │ " "│ ← qux│ │ ↓ │ 200px" "│ ↓ │ │ │ " "└─20em─┘ └──────┘ " }`, { optimize: false }); t.deepEqual(output["div > baz"], { "grid-area": "baz", "align-self": "center" }) t.deepEqual(output["div > bar"], { "grid-area": "bar", "justify-self": "end" }) t.deepEqual(output["div > foo"], { "grid-area": "foo", "align-self": "stretch" }) t.deepEqual(output["div > qux"], { "grid-area": "qux", "justify-self": "start", "align-self": "end" }) t.deepEqual(output["div"], { "display": "grid", "align-content": "stretch", "justify-content": "space-between", "grid-template-rows": "200px max-content 200px", "grid-template-columns": "20em 1fr", "grid-template-areas": '\n\t\t"baz bar"\n\t\t"baz foo"\n\t\t"qux foo"' }) }); test('other ascii formats: double segments', async t => { let output = await process( `main { grid-kiss: "╔═══════╗ ╔════════════════╗ " "║ ║ ║ .article ║ " "║ ║ ╚════════════════╝ " "║ nav ║ ╔════╗ ╔════════╗ " "║ ║ ║ ║ ║ aside ║ 320px" "╚═200px═╝ ╚════╝ ╚════════╝ " }`, { optimize: false }); t.deepEqual(output["main > nav"], { "grid-area": "nav" }) t.deepEqual(output["main > .article"], { "grid-area": "article" }) t.deepEqual(output["main > aside"], { "grid-area": "aside" }) t.deepEqual(output["main"], { "display": "grid", "align-content": "stretch", "justify-content": "space-between", "grid-template-rows": "1fr 320px", "grid-template-columns": "200px 1fr 1fr", "grid-template-areas": '\n\t\t"nav article article"\n\t\t"nav ... aside "' }) }); test('fallback properties with mixed relative/fixed', async t => { let output = await process( `body { grid-kiss: "+------------------------------+ " "| header ↑ | 120px" "+------------------------------+ " " 1em " "+--150px---+ +--- auto ----+ " "| .sidebar | | main | auto " "+----------+ +-------------+ " " 2em " "+------------------------------+ " "| ↓ | " "| → footer ← | 100px" "+------------------------------+ " " | 4vw | " }`, { fallback: true }); t.is("supports" in output, true); t.is(output["supports"].params, 'not (grid-template-areas:"test")'); t.deepEqual(output["supports"]["body"], { "position": "relative", "display": "block", "width": "100%", "height": "100%" }) t.deepEqual(output["supports"]["body > *"], { "position": "absolute", "box-sizing": "border-box", }) t.deepEqual(output["supports"]["body > header"], { "top": "0", "max-height": "120px", "left": "0", "width": "100%" }) t.deepEqual(output["supports"]["body > .sidebar"], { "top": "calc(120px + 1em)", "height": "calc(100% - 220px - 3em)", "left": "0", "width": "150px" }) t.deepEqual(output["supports"]["body > main"], { "top": "calc(120px + 1em)", "height": "calc(100% - 220px - 3em)", "left": "calc(150px + 4vw)", "width": "calc(100% - 150px - 4vw)" }) t.deepEqual(output["supports"]["body > footer"], { "bottom": "0", "max-height": "100px", "left": "50%", "max-width": "100%", "transform": "translateX(-50%)" }) }) test('fallback properties with all fixed', async t => { let output = await process( `body { grid-kiss: "┌──────┐ ┌────────────────┐ " "│ │ │ │ 100px " "│ ↑ │ │ < .bar │ " "│ .baz │ └────────────────┘ - " "│ ↓ │ ┌───────┐ ┌──────┐ " "│ │ | | │ │ 100px " "└──────┘ └───────┘ │ ↓ │ " "┌────────────────┐ │ .foo │ - " "│ .qux │ │ ↑ │ " "│ > < │ │ │ 100px " "└────────────────┘ └──────┘ " " 100px | 100px | 100px " ; }`, { browsers: ["ie 11"] }); t.is("supports" in output, true); t.is(output["supports"].params, 'not (grid-template-areas:"test")'); t.is("media" in output, true); t.is(output["media"].params, 'screen and (min-width:0\\0)'); t.deepEqual(output["supports"]["body"], { "position": "relative", "display": "block", "width": "300px", "height": "300px" }) t.deepEqual(output["supports"]["body > *"], { "position": "absolute", "box-sizing": "border-box", }) t.deepEqual(output["supports"]["body > .baz"], { "top": "0", "height": "200px", "left": "0", "width": "100px" }) t.deepEqual(output["supports"]["body > .bar"], { "top": "0", "height": "100px", "left": "100px", "max-width": "200px" }) t.deepEqual(output["supports"]["body > .qux"], { "top": "200px", "height": "100px", "left": "100px", "max-width": "200px", "transform": "translateX(-50%)" }) t.deepEqual(output["supports"]["body > .foo"], { "top": "200px", "max-height": "200px", "left": "200px", "transform": "translateY(-50%)", "width": "100px" }) }) test('fallback properties with all relative', async t => { let output = await process( `body { grid-kiss: "╔═10═╗ ╔═10═╗ " "║ .a>║ ║<.b ║ 3fr" "╚════╝ ╚════╝ " " ╔═20═╗ ╔═20═╗ " " ║ .c ║ ║ .d ║ 5fr" " ╚════╝ ╚════╝ " " ╔═30═╗ " " ║ .e ║ 7fr" " ╚════╝ " ; }`, { browsers: ["chrome 50"], optimize: false }); t.is("supports" in output, true); t.is(output["supports"].params, 'not (grid-template-areas:"test")'); t.is("media" in output, false); t.deepEqual(output["supports"]["body"], { "position": "relative", "display": "block", "width": "100%", "height": "100%" }) t.deepEqual(output["supports"]["body > *"], { "position": "absolute", "box-sizing": "border-box", }) t.deepEqual(output["supports"]["body > .a"], { "top": "0", "height": "20%", "right": "88.88889%", "max-width": "11.11111%", }) t.deepEqual(output["supports"]["body > .b"], { "top": "0", "height": "20%", "left": "88.88889%", "max-width": "11.11111%", }) t.deepEqual(output["supports"]["body > .c"], { "top": "20%", "height": "33.33333%", "left": "11.11111%", "width": "22.22222%", }) t.deepEqual(output["supports"]["body > .d"], { "top": "20%", "height": "33.33333%", "left": "66.66667%", "width": "22.22222%", }) t.deepEqual(output["supports"]["body > .e"], { "top": "53.33333%", "height": "46.66667%", "left": "33.33333%", "width": "33.33333%", }) }) test("optimize option", async t => { let output = await process( `div { grid-kiss: "+----------------+ +-----+" "|foo#bar.baz[qux]| | baz |" "+----------------+ +100px+" "+------+ +-20% -+ +-----+" "| .bar | | #foo | | qux |" "+ > 4em+ +------+ +-----+" }`, { optimize: true }) t.is(output["div"]["grid-template"], `"a a b" 1fr "c d e" 1fr / minmax(4em, auto) 20% 100px`); t.is(output["div > .bar"]["grid-area"], "c"); t.is(output["div > baz"]["grid-area"], "b"); t.is(output["div > #foo"]["grid-area"], "d"); t.is(output["div > qux"]["grid-area"], "e"); t.is(output["div > foo#bar.baz[qux]"]["grid-area"], "a"); }) test("advanced selectors", async t => { let output = await process( `div { grid-kiss: "+-------+" "| :1 |" "+-------+" " " "+-------+" "| p:2 |" "+-------+" " " "+-------+" "| Hello |" "+-------+" }`, { selectorParser: function (selector) { if (/[A-Z]/.test(selector[0])) { return `[data-component-name='${selector}']` } else return selector } } ); t.is("div > *:nth-child(1)" in output, true) t.is("div > p:nth-of-type(2)" in output, true) t.is("div > [data-component-name='Hello']" in output, true) }) test("css vars", async t => { let output = await process( `div { grid-kiss: "+-------------+ +--------------+" "| foo | | baz | var(--fooh)" "+-------------+ +--------------+" "+-------------+ +-var(--quxw) -+" "| bar | | qux |" "+-------------+ +--------------+" "| var(--barw) | | |" }`, { optimize: false }) t.is(output["div"]["grid-template-rows"], `var(--fooh) 1fr`) t.is(output["div"]["grid-template-columns"], `var(--barw) var(--quxw)`) }) test("should be able to parse inline grid declarations", async t => { let output = await process(`div { grid-kiss: "+--------------------------+ -- " "| header | 48px " "+--------------------------+ -- " "+--------+ +---------------+ -- " "| aside | | main | auto " "+--------+ +---------------+ -- " "| 48px | | auto | " ; }`); t.is("div > header" in output, true) t.is("div > aside" in output, true) t.is("div > main" in output, true) t.is(output["div"]["grid-template"], `"a a" 48px "b c " 1fr / 48px 1fr`) }) test("alternative property name", async t => { let output = await process( `div { grid-template-kiss: "+-------------+ +--------------+" "| foo | | baz |" "+-------------+ +--------------+" "+-------------+ +--------------+" "| bar | | qux |" "+-------------+ +--------------+" }`, { optimize: false }) t.is(output["div"]["grid-template-areas"], `\n\t"foo baz"\n\t"bar qux"`) })