| name | scss-tests |
| description | Writing SCSS unit tests for libs/scss-foundation using sass-true and Jest. Use this when adding or reviewing SCSS mixins, functions, or utilities in libs/scss-foundation/src/modules. |
SCSS Tests — scss-foundation
You are writing SCSS unit tests for the @enigmatry/scss-foundation library. Tests use sass-true with Jest as the test runner.
Setup and tooling
- Framework:
sass-true v10 + sass (Dart Sass) + jest
- Run all tests:
cd libs/scss-foundation && npm run test
- Run from workspace root:
npm run automated-tests (also compiles themes)
- Jest collects all files matching
tests/**/*.tests.scss via the shim in shim.test.js
File conventions
| Rule | Detail |
|---|
| File naming | <module-name>.tests.scss — must end in .tests.scss |
| Location | libs/scss-foundation/tests/<category>/ — mirror the src/modules/<category>/ folder structure |
| One file per module | Each src/modules/category/_module.scss gets its own tests/category/module.tests.scss |
Example mapping:
src/modules/typography/fonts → tests/typography/fonts.tests.scss
src/modules/borders/border-radius → tests/borders/border-radius.tests.scss
src/modules/layout/grid → tests/layout/grid.tests.scss
Import conventions
Always @use sass-true. Two forms exist in the codebase — prefer the first (alias form) for new tests, but be consistent within a file:
@use 'sass-true' as true;
@use 'sass-true/sass/true';
Import the module under test with a relative path from the test file to src/:
@use '../../src/modules/typography/fonts';
@use '../../src/modules/variables' as vars;
When the module uses a $testing guard (to suppress media queries or side-effecting output during tests), set it before any includes:
vars.$testing: true;
Test structure
Tests follow a strict three-level nesting: describe → it → assert.
@include true.describe('mixin-or-function-name($param1, $param2)') {
@include true.it('should describe expected behavior') {
@include true.assert() {
@include true.output() {
@include my-module.my-mixin(arg1, arg2);
}
@include true.expect() {
property: value;
}
}
}
}
Naming conventions
describe label: the mixin or function signature — e.g., 'generate($spacing, $divisions)', 'partial-border-radius($top-left, $top-right, $bottom-right, $bottom-left)'
it label: plain English describing the specific scenario — e.g., 'should set font family and size', 'should throw if spacing is not a number'
Choosing the right assertion
expect — exact match
Use when the mixin emits a known, deterministic set of CSS properties:
@include true.output() {
@include border.partial-border-radius($top-right: 10px, $bottom-right: 1.2em);
}
@include true.expect() {
border: {
top-left-radius: 0;
top-right-radius: 10px;
bottom-right-radius: 1.2em;
bottom-left-radius: 0;
}
}
contains — partial match
Use when the mixin emits error comments, or when the output also contains unrelated rules you don't want to assert on (e.g., responsive mixins that generate many classes):
@include true.output() {
@include grid.generate('invalid');
}
@include true.contains() {
}
Rule of thumb: use expect for normal/happy-path cases, contains for error paths and large output blocks.
Testing error/guard behavior
Sass errors emitted via @error appear as CSS comments in the compiled output. Assert them with contains:
@include true.it('should throw if only one property is defined') {
@include true.assert() {
@include true.output() {
@include fonts.define-font('Arial');
}
@include true.contains() {
}
}
}
The error comment format follows the pattern the existing modules use:
Testing optional parameters / named arguments
Test all meaningful combinations of optional parameters — defaults, named keyword arguments, and fully-specified calls:
@include true.it('should set border-radius with default values') {
@include true.assert() {
@include true.output() { @include border.partial-border-radius(); }
@include true.expect() { border: { top-left-radius: 0; ... } }
}
}
@include true.it('should set $top-right and $bottom-right') {
@include true.assert() {
@include true.output() { @include border.partial-border-radius($top-right: 10px, $bottom-right: 1.2em); }
@include true.expect() { ... }
}
}
@include true.it('should set all values') {
@include true.assert() {
@include true.output() { @include border.partial-border-radius(4px, 4px, 8px, 8px); }
@include true.expect() { ... }
}
}
Testing hover / pseudo-states
When mixins emit nested selectors (e.g., &:hover), write them directly in expect:
@include true.output() {
@include hover.background-hover(#FFF, #CCC);
}
@include true.expect() {
background-color: #FFF;
&:hover, &:hover::before {
background-color: #CCC;
}
}
Add /* stylelint-disable-next-line nesting-selector-no-missing-scoping-root */ before any nested & selector inside expect to suppress stylelint false positives.
Suppressing stylelint in test files
Test files frequently need to suppress rules that are intentional in the context of assertions. Add file-level disables at the top when needed:
Only disable what is actually needed for the file.
Coverage checklist when writing tests for a new mixin
- Happy path with all arguments — verify exact output
- Happy path with defaults only — verify defaults are applied
- Happy path with named/keyword arguments — verify partial overrides
- Error/guard paths — verify error comments are emitted for invalid inputs
- Edge values — zero,
null, wrong type, out-of-range numbers (where the mixin guards against them)