// Output tests for the `generate-utility` mixin from // scss/mixins/bootstrap/_utilities.scss — the engine behind Tabler's utility // classes. `@use "../mixins/bootstrap/utilities"` pulls in the real // settings/variables chain, so `$enable-important-utilities` = true (hence the // `!important` in the output). Custom properties are expected unprefixed: the // `--tblr-` prefix is added later, by the css pipeline (.build/css-var-prefix.ts). // // Each `it` feeds the mixin a hand-built utility map and asserts the generated // classes, covering the branches that shape the output: map vs list values, // single vs multiple properties, custom class, css-var mode, pseudo-class // states and the responsive infix. @use 'sass:map'; @use '../mixins/bootstrap/utilities' as *; @use '../mixins/bootstrap/breakpoints' as *; @include describe('generate-utility') { @include it('generates a class per key from a values map') { @include assert() { @include output() { @include generate-utility( ( property: opacity, values: ( 0: 0, 50: 0.5, ), ) ); } @include expect() { .opacity-0 { opacity: 0 !important; } .opacity-50 { opacity: 0.5 !important; } } } } @include it('zips a plain list of values into key/value pairs') { @include assert() { @include output() { @include generate-utility( ( property: float, class: float, values: ( left, right, ), ) ); } @include expect() { .float-left { float: left !important; } .float-right { float: right !important; } } } } @include it('emits every property when several are given') { @include assert() { @include output() { @include generate-utility( ( property: margin-inline-end margin-inline-start, class: mx, values: ( 0: 0, auto: auto, ), ) ); } @include expect() { .mx-0 { margin-inline-end: 0 !important; margin-inline-start: 0 !important; } .mx-auto { margin-inline-end: auto !important; margin-inline-start: auto !important; } } } } @include it('sets a custom property instead of a declaration in css-var mode') { @include assert() { @include output() { @include generate-utility( ( property: opacity, class: opacity, css-var: true, values: ( 50: 0.5, ), ) ); } @include expect() { .opacity-50 { --opacity: 0.5; } } } } @include it('adds a pseudo-class variant for each declared state') { @include assert() { @include output() { @include generate-utility( ( property: text-decoration, class: text-decoration, state: hover, values: (underline), ) ); } @include expect() { .text-decoration-underline { text-decoration: underline !important; } .text-decoration-underline-hover:hover { text-decoration: underline !important; } } } } @include it('injects the responsive infix into the class name') { @include assert() { @include output() { @include generate-utility( ( property: display, class: d, values: (none), ), '-md' ); } @include expect() { .d-md-none { display: none !important; } } } } } // `generate-utility` itself does not read the `responsive` key — that gate lives // in bootstrap/utilities/_api.scss, which loops the breakpoints and only calls // the mixin for a given infix when `map.get($utility, responsive)` is truthy (or // it's the base, infix-less breakpoint). This reproduces that gate against a local // breakpoint map with two utilities: one `responsive: true`, one without. @include describe('generate-utility (responsive property)') { $g: ( xs: 0, sm: 576px, md: 768px, ); $utils: ( 'display': ( property: display, class: d, responsive: true, values: ( none, flex, ), ), 'float': ( property: float, class: float, values: ( start: inline-start, end: inline-end, none: none, ), ), ); @include it('only a `responsive: true` utility gets breakpoint-infixed variants') { @include assert() { @include output() { @each $bp in map.keys($g) { @include media-breakpoint-up($bp, $g) { $infix: breakpoint-infix($bp, $g); @each $key, $utility in $utils { @if map.get($utility, responsive) or $infix == '' { @include generate-utility($utility, $infix); } } } } } @include expect() { // base breakpoint: both utilities emit bare classes .d-none { display: none !important; } .d-flex { display: flex !important; } .float-start { float: inline-start !important; } .float-end { float: inline-end !important; } .float-none { float: none !important; } // responsive breakpoints: only `d` (responsive: true) is repeated; `float` is not @media (min-width: 576px) { .d-sm-none { display: none !important; } .d-sm-flex { display: flex !important; } } @media (min-width: 768px) { .d-md-none { display: none !important; } .d-md-flex { display: flex !important; } } } } } }