@jesscss/core 2.0.0-alpha.5 → 2.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.cjs +20159 -0
- package/lib/index.d.cts +5993 -0
- package/lib/index.d.cts.map +1 -0
- package/lib/index.d.ts +5992 -21
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +19926 -22
- package/lib/index.js.map +1 -1
- package/package.json +15 -14
- package/src/__tests__/define-function-record.test.ts +58 -0
- package/src/__tests__/define-function-simple.test.ts +55 -0
- package/src/__tests__/define-function-split-sequence.test.ts +547 -0
- package/src/__tests__/define-function-type-parity.test.ts +9 -0
- package/src/__tests__/define-function.test.ts +763 -0
- package/src/__tests__/num-operations.test.ts +91 -0
- package/src/__tests__/safe-parse.test.ts +374 -0
- package/src/context.ts +896 -0
- package/src/conversions.ts +282 -0
- package/src/debug-log.ts +29 -0
- package/src/define-function.ts +1006 -0
- package/src/deprecation.ts +67 -0
- package/src/globals.d.ts +26 -0
- package/src/index.ts +31 -0
- package/src/jess-error.ts +773 -0
- package/src/logger/deprecation-processing.ts +109 -0
- package/src/logger.ts +31 -0
- package/src/plugin.ts +292 -0
- package/src/tree/LOOKUP_CHAINS.md +35 -0
- package/src/tree/README.md +18 -0
- package/src/tree/__tests__/__snapshots__/extend-eval-integration.test.ts.snap +1455 -0
- package/src/tree/__tests__/ampersand.test.ts +382 -0
- package/src/tree/__tests__/at-rule.test.ts +2047 -0
- package/src/tree/__tests__/basic-render.test.ts +212 -0
- package/src/tree/__tests__/block.test.ts +40 -0
- package/src/tree/__tests__/call.test.ts +346 -0
- package/src/tree/__tests__/color.test.ts +537 -0
- package/src/tree/__tests__/condition.test.ts +186 -0
- package/src/tree/__tests__/control.test.ts +564 -0
- package/src/tree/__tests__/declaration.test.ts +253 -0
- package/src/tree/__tests__/dependency-graph.test.ts +177 -0
- package/src/tree/__tests__/detached-rulesets.test.ts +213 -0
- package/src/tree/__tests__/dimension.test.ts +236 -0
- package/src/tree/__tests__/expression.test.ts +73 -0
- package/src/tree/__tests__/ext-node.test.ts +31 -0
- package/src/tree/__tests__/extend-eval-integration.test.ts +1033 -0
- package/src/tree/__tests__/extend-import-style.test.ts +929 -0
- package/src/tree/__tests__/extend-less-fixtures.test.ts +851 -0
- package/src/tree/__tests__/extend-list.test.ts +31 -0
- package/src/tree/__tests__/extend-roots.test.ts +1045 -0
- package/src/tree/__tests__/extend-rules.test.ts +740 -0
- package/src/tree/__tests__/func.test.ts +171 -0
- package/src/tree/__tests__/import-js.test.ts +33 -0
- package/src/tree/__tests__/import-style-test-helpers.ts +56 -0
- package/src/tree/__tests__/import-style.test.ts +1967 -0
- package/src/tree/__tests__/interpolated-reference.test.ts +44 -0
- package/src/tree/__tests__/interpolated.test.ts +41 -0
- package/src/tree/__tests__/list.test.ts +177 -0
- package/src/tree/__tests__/log.test.ts +83 -0
- package/src/tree/__tests__/mixin-recursion.test.ts +639 -0
- package/src/tree/__tests__/mixin.test.ts +2171 -0
- package/src/tree/__tests__/negative.test.ts +45 -0
- package/src/tree/__tests__/nesting-collapse.test.ts +519 -0
- package/src/tree/__tests__/node-flags-perf.test.ts +195 -0
- package/src/tree/__tests__/node-flags.test.ts +410 -0
- package/src/tree/__tests__/node-graph.test.ts +598 -0
- package/src/tree/__tests__/node-mutation.test.ts +182 -0
- package/src/tree/__tests__/operation.test.ts +18 -0
- package/src/tree/__tests__/paren.test.ts +90 -0
- package/src/tree/__tests__/preserve-mode-output.test.ts +50 -0
- package/src/tree/__tests__/quoted.test.ts +72 -0
- package/src/tree/__tests__/range.test.ts +59 -0
- package/src/tree/__tests__/reference.test.ts +743 -0
- package/src/tree/__tests__/rest.test.ts +29 -0
- package/src/tree/__tests__/rules-raw.test.ts +14 -0
- package/src/tree/__tests__/rules.test.ts +1271 -0
- package/src/tree/__tests__/ruleset.test.ts +597 -0
- package/src/tree/__tests__/selector-attr.test.ts +50 -0
- package/src/tree/__tests__/selector-basic.test.ts +44 -0
- package/src/tree/__tests__/selector-capture.test.ts +22 -0
- package/src/tree/__tests__/selector-complex.test.ts +120 -0
- package/src/tree/__tests__/selector-compound.test.ts +74 -0
- package/src/tree/__tests__/selector-interpolated.test.ts +50 -0
- package/src/tree/__tests__/selector-list.test.ts +59 -0
- package/src/tree/__tests__/selector-pseudo.test.ts +23 -0
- package/src/tree/__tests__/selector.test.ts +182 -0
- package/src/tree/__tests__/sequence.test.ts +226 -0
- package/src/tree/__tests__/serialize-types.test.ts +529 -0
- package/src/tree/__tests__/spaced.test.ts +8 -0
- package/src/tree/__tests__/url.test.ts +72 -0
- package/src/tree/__tests__/var-declaration.test.ts +90 -0
- package/src/tree/ampersand.ts +538 -0
- package/src/tree/any.ts +169 -0
- package/src/tree/at-rule.ts +760 -0
- package/src/tree/block.ts +72 -0
- package/src/tree/bool.ts +46 -0
- package/src/tree/call.ts +593 -0
- package/src/tree/collection.ts +52 -0
- package/src/tree/color.ts +629 -0
- package/src/tree/combinator.ts +30 -0
- package/src/tree/comment.ts +36 -0
- package/src/tree/condition.ts +194 -0
- package/src/tree/control.ts +452 -0
- package/src/tree/declaration-custom.ts +56 -0
- package/src/tree/declaration-var.ts +87 -0
- package/src/tree/declaration.ts +742 -0
- package/src/tree/default-guard.ts +35 -0
- package/src/tree/dimension.ts +392 -0
- package/src/tree/expression.ts +97 -0
- package/src/tree/extend-list.ts +51 -0
- package/src/tree/extend.ts +391 -0
- package/src/tree/function.ts +254 -0
- package/src/tree/import-js.ts +130 -0
- package/src/tree/import-style.ts +875 -0
- package/{lib/tree/index.js → src/tree/index.ts} +49 -22
- package/src/tree/interpolated.ts +346 -0
- package/src/tree/js-array.ts +21 -0
- package/src/tree/js-expr.ts +50 -0
- package/src/tree/js-function.ts +31 -0
- package/src/tree/js-object.ts +22 -0
- package/src/tree/list.ts +415 -0
- package/src/tree/log.ts +89 -0
- package/src/tree/mixin.ts +331 -0
- package/src/tree/negative.ts +58 -0
- package/src/tree/nil.ts +57 -0
- package/src/tree/node-base.ts +1716 -0
- package/src/tree/node-type.ts +122 -0
- package/src/tree/node.ts +118 -0
- package/src/tree/number.ts +54 -0
- package/src/tree/operation.ts +187 -0
- package/src/tree/paren.ts +132 -0
- package/src/tree/query-condition.ts +47 -0
- package/src/tree/quoted.ts +119 -0
- package/src/tree/range.ts +101 -0
- package/src/tree/reference.ts +1099 -0
- package/src/tree/rest.ts +55 -0
- package/src/tree/rules-raw.ts +52 -0
- package/src/tree/rules.ts +2896 -0
- package/src/tree/ruleset.ts +1217 -0
- package/src/tree/selector-attr.ts +172 -0
- package/src/tree/selector-basic.ts +75 -0
- package/src/tree/selector-capture.ts +85 -0
- package/src/tree/selector-complex.ts +189 -0
- package/src/tree/selector-compound.ts +205 -0
- package/src/tree/selector-interpolated.ts +95 -0
- package/src/tree/selector-list.ts +245 -0
- package/src/tree/selector-pseudo.ts +173 -0
- package/src/tree/selector-simple.ts +10 -0
- package/src/tree/selector.ts +152 -0
- package/src/tree/sequence.ts +463 -0
- package/src/tree/tree.ts +130 -0
- package/src/tree/url.ts +95 -0
- package/src/tree/util/EXTEND_ARCHITECTURE_ANALYSIS.md +215 -0
- package/src/tree/util/EXTEND_AUDIT.md +233 -0
- package/src/tree/util/EXTEND_BASELINE.md +64 -0
- package/src/tree/util/EXTEND_CALL_GRAPH_ANALYSIS.md +244 -0
- package/src/tree/util/EXTEND_DOCS.md +24 -0
- package/src/tree/util/EXTEND_FINAL_SUMMARY.md +95 -0
- package/src/tree/util/EXTEND_FUNCTION_AUDIT.md +1433 -0
- package/src/tree/util/EXTEND_OPTIMIZATION_PLAN.md +114 -0
- package/src/tree/util/EXTEND_REFACTORING_SUMMARY.md +152 -0
- package/src/tree/util/EXTEND_RULES.md +74 -0
- package/src/tree/util/EXTEND_UNUSED_FUNCTIONS.md +127 -0
- package/src/tree/util/EXTEND_UNUSED_FUNCTIONS_ANALYSIS.md +227 -0
- package/src/tree/util/NODE_COPY_REDUCTION_PLAN.md +12 -0
- package/src/tree/util/__tests__/EXTEND_TEST_INDEX.md +59 -0
- package/src/tree/util/__tests__/OPTIMIZATION-ANALYSIS.md +130 -0
- package/src/tree/util/__tests__/WALK_AND_CONSUME_DESIGN.md +138 -0
- package/src/tree/util/__tests__/_archive/2026-02-09__OPTIMIZATION-ANALYSIS.md +9 -0
- package/src/tree/util/__tests__/_archive/README.md +4 -0
- package/src/tree/util/__tests__/bitset.test.ts +142 -0
- package/src/tree/util/__tests__/debug-log.ts +50 -0
- package/src/tree/util/__tests__/extend-comment-handling.test.ts +187 -0
- package/src/tree/util/__tests__/extend-core-unit.test.ts +941 -0
- package/src/tree/util/__tests__/extend-pipeline-bench.test.ts +154 -0
- package/src/tree/util/__tests__/extend-pipeline-bench.ts +190 -0
- package/src/tree/util/__tests__/fast-reject.test.ts +377 -0
- package/src/tree/util/__tests__/is-node.test.ts +63 -0
- package/src/tree/util/__tests__/list-like.test.ts +63 -0
- package/src/tree/util/__tests__/outputwriter.test.ts +523 -0
- package/src/tree/util/__tests__/print.test.ts +183 -0
- package/src/tree/util/__tests__/process-extends.test.ts +226 -0
- package/src/tree/util/__tests__/process-leading-is.test.ts +205 -0
- package/src/tree/util/__tests__/recursion-helper.test.ts +184 -0
- package/src/tree/util/__tests__/selector-match-unit.test.ts +1427 -0
- package/src/tree/util/__tests__/sourcemap.test.ts +117 -0
- package/src/tree/util/ampersand-template.ts +9 -0
- package/src/tree/util/bitset.ts +194 -0
- package/src/tree/util/calculate.ts +11 -0
- package/src/tree/util/cast.ts +89 -0
- package/src/tree/util/cloning.ts +8 -0
- package/src/tree/util/collections.ts +299 -0
- package/src/tree/util/compare.ts +90 -0
- package/src/tree/util/cursor.ts +171 -0
- package/src/tree/util/extend-core.ts +2139 -0
- package/src/tree/util/extend-roots.ts +1108 -0
- package/src/tree/util/field-helpers.ts +354 -0
- package/src/tree/util/is-node.ts +43 -0
- package/src/tree/util/list-like.ts +93 -0
- package/src/tree/util/mixin-instance-primitives.ts +2020 -0
- package/src/tree/util/print.ts +303 -0
- package/src/tree/util/process-leading-is.ts +421 -0
- package/src/tree/util/recursion-helper.ts +54 -0
- package/src/tree/util/regex.ts +2 -0
- package/src/tree/util/registry-utils.ts +1953 -0
- package/src/tree/util/ruleset-trace.ts +17 -0
- package/src/tree/util/scoped-body-eval.ts +320 -0
- package/src/tree/util/selector-match-core.ts +2005 -0
- package/src/tree/util/selector-utils.ts +757 -0
- package/src/tree/util/serialize-helper.ts +535 -0
- package/src/tree/util/serialize-types.ts +318 -0
- package/src/tree/util/should-operate.ts +78 -0
- package/src/tree/util/sourcemap.ts +37 -0
- package/src/types/config.ts +247 -0
- package/src/types/index.ts +12 -0
- package/{lib/types/modes.d.ts → src/types/modes.ts} +2 -1
- package/src/types.d.ts +9 -0
- package/src/types.ts +68 -0
- package/src/use-webpack-resolver.ts +56 -0
- package/src/visitor/__tests__/visitor.test.ts +136 -0
- package/src/visitor/index.ts +263 -0
- package/{lib/visitor/less-visitor.js → src/visitor/less-visitor.ts} +3 -2
- package/lib/context.d.ts +0 -352
- package/lib/context.d.ts.map +0 -1
- package/lib/context.js +0 -636
- package/lib/context.js.map +0 -1
- package/lib/conversions.d.ts +0 -73
- package/lib/conversions.d.ts.map +0 -1
- package/lib/conversions.js +0 -253
- package/lib/conversions.js.map +0 -1
- package/lib/debug-log.d.ts +0 -2
- package/lib/debug-log.d.ts.map +0 -1
- package/lib/debug-log.js +0 -27
- package/lib/debug-log.js.map +0 -1
- package/lib/define-function.d.ts +0 -587
- package/lib/define-function.d.ts.map +0 -1
- package/lib/define-function.js +0 -726
- package/lib/define-function.js.map +0 -1
- package/lib/deprecation.d.ts +0 -34
- package/lib/deprecation.d.ts.map +0 -1
- package/lib/deprecation.js +0 -57
- package/lib/deprecation.js.map +0 -1
- package/lib/jess-error.d.ts +0 -343
- package/lib/jess-error.d.ts.map +0 -1
- package/lib/jess-error.js +0 -508
- package/lib/jess-error.js.map +0 -1
- package/lib/logger/deprecation-processing.d.ts +0 -41
- package/lib/logger/deprecation-processing.d.ts.map +0 -1
- package/lib/logger/deprecation-processing.js +0 -81
- package/lib/logger/deprecation-processing.js.map +0 -1
- package/lib/logger.d.ts +0 -10
- package/lib/logger.d.ts.map +0 -1
- package/lib/logger.js +0 -20
- package/lib/logger.js.map +0 -1
- package/lib/plugin.d.ts +0 -94
- package/lib/plugin.d.ts.map +0 -1
- package/lib/plugin.js +0 -174
- package/lib/plugin.js.map +0 -1
- package/lib/tree/ampersand.d.ts +0 -98
- package/lib/tree/ampersand.d.ts.map +0 -1
- package/lib/tree/ampersand.js +0 -319
- package/lib/tree/ampersand.js.map +0 -1
- package/lib/tree/any.d.ts +0 -58
- package/lib/tree/any.d.ts.map +0 -1
- package/lib/tree/any.js +0 -104
- package/lib/tree/any.js.map +0 -1
- package/lib/tree/at-rule.d.ts +0 -53
- package/lib/tree/at-rule.d.ts.map +0 -1
- package/lib/tree/at-rule.js +0 -503
- package/lib/tree/at-rule.js.map +0 -1
- package/lib/tree/block.d.ts +0 -22
- package/lib/tree/block.d.ts.map +0 -1
- package/lib/tree/block.js +0 -24
- package/lib/tree/block.js.map +0 -1
- package/lib/tree/bool.d.ts +0 -18
- package/lib/tree/bool.d.ts.map +0 -1
- package/lib/tree/bool.js +0 -28
- package/lib/tree/bool.js.map +0 -1
- package/lib/tree/call.d.ts +0 -66
- package/lib/tree/call.d.ts.map +0 -1
- package/lib/tree/call.js +0 -306
- package/lib/tree/call.js.map +0 -1
- package/lib/tree/collection.d.ts +0 -30
- package/lib/tree/collection.d.ts.map +0 -1
- package/lib/tree/collection.js +0 -37
- package/lib/tree/collection.js.map +0 -1
- package/lib/tree/color.d.ts +0 -101
- package/lib/tree/color.d.ts.map +0 -1
- package/lib/tree/color.js +0 -513
- package/lib/tree/color.js.map +0 -1
- package/lib/tree/combinator.d.ts +0 -13
- package/lib/tree/combinator.d.ts.map +0 -1
- package/lib/tree/combinator.js +0 -12
- package/lib/tree/combinator.js.map +0 -1
- package/lib/tree/comment.d.ts +0 -20
- package/lib/tree/comment.d.ts.map +0 -1
- package/lib/tree/comment.js +0 -19
- package/lib/tree/comment.js.map +0 -1
- package/lib/tree/condition.d.ts +0 -31
- package/lib/tree/condition.d.ts.map +0 -1
- package/lib/tree/condition.js +0 -103
- package/lib/tree/condition.js.map +0 -1
- package/lib/tree/control.d.ts +0 -104
- package/lib/tree/control.d.ts.map +0 -1
- package/lib/tree/control.js +0 -430
- package/lib/tree/control.js.map +0 -1
- package/lib/tree/declaration-custom.d.ts +0 -18
- package/lib/tree/declaration-custom.d.ts.map +0 -1
- package/lib/tree/declaration-custom.js +0 -24
- package/lib/tree/declaration-custom.js.map +0 -1
- package/lib/tree/declaration-var.d.ts +0 -35
- package/lib/tree/declaration-var.d.ts.map +0 -1
- package/lib/tree/declaration-var.js +0 -63
- package/lib/tree/declaration-var.js.map +0 -1
- package/lib/tree/declaration.d.ts +0 -78
- package/lib/tree/declaration.d.ts.map +0 -1
- package/lib/tree/declaration.js +0 -286
- package/lib/tree/declaration.js.map +0 -1
- package/lib/tree/default-guard.d.ts +0 -15
- package/lib/tree/default-guard.d.ts.map +0 -1
- package/lib/tree/default-guard.js +0 -19
- package/lib/tree/default-guard.js.map +0 -1
- package/lib/tree/dimension.d.ts +0 -34
- package/lib/tree/dimension.d.ts.map +0 -1
- package/lib/tree/dimension.js +0 -294
- package/lib/tree/dimension.js.map +0 -1
- package/lib/tree/expression.d.ts +0 -25
- package/lib/tree/expression.d.ts.map +0 -1
- package/lib/tree/expression.js +0 -32
- package/lib/tree/expression.js.map +0 -1
- package/lib/tree/extend-list.d.ts +0 -23
- package/lib/tree/extend-list.d.ts.map +0 -1
- package/lib/tree/extend-list.js +0 -23
- package/lib/tree/extend-list.js.map +0 -1
- package/lib/tree/extend.d.ts +0 -47
- package/lib/tree/extend.d.ts.map +0 -1
- package/lib/tree/extend.js +0 -296
- package/lib/tree/extend.js.map +0 -1
- package/lib/tree/function.d.ts +0 -48
- package/lib/tree/function.d.ts.map +0 -1
- package/lib/tree/function.js +0 -74
- package/lib/tree/function.js.map +0 -1
- package/lib/tree/import-js.d.ts +0 -35
- package/lib/tree/import-js.d.ts.map +0 -1
- package/lib/tree/import-js.js +0 -45
- package/lib/tree/import-js.js.map +0 -1
- package/lib/tree/import-style.d.ts +0 -156
- package/lib/tree/import-style.d.ts.map +0 -1
- package/lib/tree/import-style.js +0 -566
- package/lib/tree/import-style.js.map +0 -1
- package/lib/tree/index.d.ts +0 -71
- package/lib/tree/index.d.ts.map +0 -1
- package/lib/tree/index.js.map +0 -1
- package/lib/tree/interpolated-reference.d.ts +0 -24
- package/lib/tree/interpolated-reference.d.ts.map +0 -1
- package/lib/tree/interpolated-reference.js +0 -37
- package/lib/tree/interpolated-reference.js.map +0 -1
- package/lib/tree/interpolated.d.ts +0 -62
- package/lib/tree/interpolated.d.ts.map +0 -1
- package/lib/tree/interpolated.js +0 -204
- package/lib/tree/interpolated.js.map +0 -1
- package/lib/tree/js-array.d.ts +0 -10
- package/lib/tree/js-array.d.ts.map +0 -1
- package/lib/tree/js-array.js +0 -10
- package/lib/tree/js-array.js.map +0 -1
- package/lib/tree/js-expr.d.ts +0 -23
- package/lib/tree/js-expr.d.ts.map +0 -1
- package/lib/tree/js-expr.js +0 -28
- package/lib/tree/js-expr.js.map +0 -1
- package/lib/tree/js-function.d.ts +0 -20
- package/lib/tree/js-function.d.ts.map +0 -1
- package/lib/tree/js-function.js +0 -16
- package/lib/tree/js-function.js.map +0 -1
- package/lib/tree/js-object.d.ts +0 -10
- package/lib/tree/js-object.d.ts.map +0 -1
- package/lib/tree/js-object.js +0 -10
- package/lib/tree/js-object.js.map +0 -1
- package/lib/tree/list.d.ts +0 -38
- package/lib/tree/list.d.ts.map +0 -1
- package/lib/tree/list.js +0 -83
- package/lib/tree/list.js.map +0 -1
- package/lib/tree/log.d.ts +0 -29
- package/lib/tree/log.d.ts.map +0 -1
- package/lib/tree/log.js +0 -56
- package/lib/tree/log.js.map +0 -1
- package/lib/tree/mixin.d.ts +0 -87
- package/lib/tree/mixin.d.ts.map +0 -1
- package/lib/tree/mixin.js +0 -112
- package/lib/tree/mixin.js.map +0 -1
- package/lib/tree/negative.d.ts +0 -17
- package/lib/tree/negative.d.ts.map +0 -1
- package/lib/tree/negative.js +0 -22
- package/lib/tree/negative.js.map +0 -1
- package/lib/tree/nil.d.ts +0 -30
- package/lib/tree/nil.d.ts.map +0 -1
- package/lib/tree/nil.js +0 -35
- package/lib/tree/nil.js.map +0 -1
- package/lib/tree/node-base.d.ts +0 -361
- package/lib/tree/node-base.d.ts.map +0 -1
- package/lib/tree/node-base.js +0 -930
- package/lib/tree/node-base.js.map +0 -1
- package/lib/tree/node.d.ts +0 -10
- package/lib/tree/node.d.ts.map +0 -1
- package/lib/tree/node.js +0 -45
- package/lib/tree/node.js.map +0 -1
- package/lib/tree/number.d.ts +0 -21
- package/lib/tree/number.d.ts.map +0 -1
- package/lib/tree/number.js +0 -27
- package/lib/tree/number.js.map +0 -1
- package/lib/tree/operation.d.ts +0 -26
- package/lib/tree/operation.d.ts.map +0 -1
- package/lib/tree/operation.js +0 -103
- package/lib/tree/operation.js.map +0 -1
- package/lib/tree/paren.d.ts +0 -19
- package/lib/tree/paren.d.ts.map +0 -1
- package/lib/tree/paren.js +0 -92
- package/lib/tree/paren.js.map +0 -1
- package/lib/tree/query-condition.d.ts +0 -17
- package/lib/tree/query-condition.d.ts.map +0 -1
- package/lib/tree/query-condition.js +0 -39
- package/lib/tree/query-condition.js.map +0 -1
- package/lib/tree/quoted.d.ts +0 -28
- package/lib/tree/quoted.d.ts.map +0 -1
- package/lib/tree/quoted.js +0 -75
- package/lib/tree/quoted.js.map +0 -1
- package/lib/tree/range.d.ts +0 -33
- package/lib/tree/range.d.ts.map +0 -1
- package/lib/tree/range.js +0 -47
- package/lib/tree/range.js.map +0 -1
- package/lib/tree/reference.d.ts +0 -76
- package/lib/tree/reference.d.ts.map +0 -1
- package/lib/tree/reference.js +0 -521
- package/lib/tree/reference.js.map +0 -1
- package/lib/tree/rest.d.ts +0 -15
- package/lib/tree/rest.d.ts.map +0 -1
- package/lib/tree/rest.js +0 -32
- package/lib/tree/rest.js.map +0 -1
- package/lib/tree/rules-raw.d.ts +0 -17
- package/lib/tree/rules-raw.d.ts.map +0 -1
- package/lib/tree/rules-raw.js +0 -37
- package/lib/tree/rules-raw.js.map +0 -1
- package/lib/tree/rules.d.ts +0 -262
- package/lib/tree/rules.d.ts.map +0 -1
- package/lib/tree/rules.js +0 -2359
- package/lib/tree/rules.js.map +0 -1
- package/lib/tree/ruleset.d.ts +0 -92
- package/lib/tree/ruleset.d.ts.map +0 -1
- package/lib/tree/ruleset.js +0 -528
- package/lib/tree/ruleset.js.map +0 -1
- package/lib/tree/selector-attr.d.ts +0 -31
- package/lib/tree/selector-attr.d.ts.map +0 -1
- package/lib/tree/selector-attr.js +0 -99
- package/lib/tree/selector-attr.js.map +0 -1
- package/lib/tree/selector-basic.d.ts +0 -24
- package/lib/tree/selector-basic.d.ts.map +0 -1
- package/lib/tree/selector-basic.js +0 -38
- package/lib/tree/selector-basic.js.map +0 -1
- package/lib/tree/selector-capture.d.ts +0 -23
- package/lib/tree/selector-capture.d.ts.map +0 -1
- package/lib/tree/selector-capture.js +0 -34
- package/lib/tree/selector-capture.js.map +0 -1
- package/lib/tree/selector-complex.d.ts +0 -40
- package/lib/tree/selector-complex.d.ts.map +0 -1
- package/lib/tree/selector-complex.js +0 -143
- package/lib/tree/selector-complex.js.map +0 -1
- package/lib/tree/selector-compound.d.ts +0 -16
- package/lib/tree/selector-compound.d.ts.map +0 -1
- package/lib/tree/selector-compound.js +0 -114
- package/lib/tree/selector-compound.js.map +0 -1
- package/lib/tree/selector-interpolated.d.ts +0 -23
- package/lib/tree/selector-interpolated.d.ts.map +0 -1
- package/lib/tree/selector-interpolated.js +0 -27
- package/lib/tree/selector-interpolated.js.map +0 -1
- package/lib/tree/selector-list.d.ts +0 -17
- package/lib/tree/selector-list.d.ts.map +0 -1
- package/lib/tree/selector-list.js +0 -174
- package/lib/tree/selector-list.js.map +0 -1
- package/lib/tree/selector-pseudo.d.ts +0 -42
- package/lib/tree/selector-pseudo.d.ts.map +0 -1
- package/lib/tree/selector-pseudo.js +0 -204
- package/lib/tree/selector-pseudo.js.map +0 -1
- package/lib/tree/selector-simple.d.ts +0 -5
- package/lib/tree/selector-simple.d.ts.map +0 -1
- package/lib/tree/selector-simple.js +0 -6
- package/lib/tree/selector-simple.js.map +0 -1
- package/lib/tree/selector.d.ts +0 -43
- package/lib/tree/selector.d.ts.map +0 -1
- package/lib/tree/selector.js +0 -56
- package/lib/tree/selector.js.map +0 -1
- package/lib/tree/sequence.d.ts +0 -43
- package/lib/tree/sequence.d.ts.map +0 -1
- package/lib/tree/sequence.js +0 -151
- package/lib/tree/sequence.js.map +0 -1
- package/lib/tree/tree.d.ts +0 -87
- package/lib/tree/tree.d.ts.map +0 -1
- package/lib/tree/tree.js +0 -2
- package/lib/tree/tree.js.map +0 -1
- package/lib/tree/url.d.ts +0 -18
- package/lib/tree/url.d.ts.map +0 -1
- package/lib/tree/url.js +0 -35
- package/lib/tree/url.js.map +0 -1
- package/lib/tree/util/__tests__/debug-log.d.ts +0 -1
- package/lib/tree/util/__tests__/debug-log.d.ts.map +0 -1
- package/lib/tree/util/__tests__/debug-log.js +0 -36
- package/lib/tree/util/__tests__/debug-log.js.map +0 -1
- package/lib/tree/util/calculate.d.ts +0 -3
- package/lib/tree/util/calculate.d.ts.map +0 -1
- package/lib/tree/util/calculate.js +0 -10
- package/lib/tree/util/calculate.js.map +0 -1
- package/lib/tree/util/cast.d.ts +0 -10
- package/lib/tree/util/cast.d.ts.map +0 -1
- package/lib/tree/util/cast.js +0 -87
- package/lib/tree/util/cast.js.map +0 -1
- package/lib/tree/util/cloning.d.ts +0 -4
- package/lib/tree/util/cloning.d.ts.map +0 -1
- package/lib/tree/util/cloning.js +0 -8
- package/lib/tree/util/cloning.js.map +0 -1
- package/lib/tree/util/collections.d.ts +0 -57
- package/lib/tree/util/collections.d.ts.map +0 -1
- package/lib/tree/util/collections.js +0 -136
- package/lib/tree/util/collections.js.map +0 -1
- package/lib/tree/util/compare.d.ts +0 -11
- package/lib/tree/util/compare.d.ts.map +0 -1
- package/lib/tree/util/compare.js +0 -89
- package/lib/tree/util/compare.js.map +0 -1
- package/lib/tree/util/extend-helpers.d.ts +0 -2
- package/lib/tree/util/extend-helpers.d.ts.map +0 -1
- package/lib/tree/util/extend-helpers.js +0 -2
- package/lib/tree/util/extend-helpers.js.map +0 -1
- package/lib/tree/util/extend-roots.d.ts +0 -37
- package/lib/tree/util/extend-roots.d.ts.map +0 -1
- package/lib/tree/util/extend-roots.js +0 -700
- package/lib/tree/util/extend-roots.js.map +0 -1
- package/lib/tree/util/extend-roots.old.d.ts +0 -132
- package/lib/tree/util/extend-roots.old.d.ts.map +0 -1
- package/lib/tree/util/extend-roots.old.js +0 -2272
- package/lib/tree/util/extend-roots.old.js.map +0 -1
- package/lib/tree/util/extend-trace-debug.d.ts +0 -13
- package/lib/tree/util/extend-trace-debug.d.ts.map +0 -1
- package/lib/tree/util/extend-trace-debug.js +0 -34
- package/lib/tree/util/extend-trace-debug.js.map +0 -1
- package/lib/tree/util/extend-walk.d.ts +0 -53
- package/lib/tree/util/extend-walk.d.ts.map +0 -1
- package/lib/tree/util/extend-walk.js +0 -881
- package/lib/tree/util/extend-walk.js.map +0 -1
- package/lib/tree/util/extend.d.ts +0 -218
- package/lib/tree/util/extend.d.ts.map +0 -1
- package/lib/tree/util/extend.js +0 -3182
- package/lib/tree/util/extend.js.map +0 -1
- package/lib/tree/util/find-extendable-locations.d.ts +0 -2
- package/lib/tree/util/find-extendable-locations.d.ts.map +0 -1
- package/lib/tree/util/find-extendable-locations.js +0 -2
- package/lib/tree/util/find-extendable-locations.js.map +0 -1
- package/lib/tree/util/format.d.ts +0 -20
- package/lib/tree/util/format.d.ts.map +0 -1
- package/lib/tree/util/format.js +0 -67
- package/lib/tree/util/format.js.map +0 -1
- package/lib/tree/util/is-node.d.ts +0 -13
- package/lib/tree/util/is-node.d.ts.map +0 -1
- package/lib/tree/util/is-node.js +0 -43
- package/lib/tree/util/is-node.js.map +0 -1
- package/lib/tree/util/print.d.ts +0 -80
- package/lib/tree/util/print.d.ts.map +0 -1
- package/lib/tree/util/print.js +0 -205
- package/lib/tree/util/print.js.map +0 -1
- package/lib/tree/util/process-leading-is.d.ts +0 -25
- package/lib/tree/util/process-leading-is.d.ts.map +0 -1
- package/lib/tree/util/process-leading-is.js +0 -364
- package/lib/tree/util/process-leading-is.js.map +0 -1
- package/lib/tree/util/recursion-helper.d.ts +0 -15
- package/lib/tree/util/recursion-helper.d.ts.map +0 -1
- package/lib/tree/util/recursion-helper.js +0 -43
- package/lib/tree/util/recursion-helper.js.map +0 -1
- package/lib/tree/util/regex.d.ts +0 -4
- package/lib/tree/util/regex.d.ts.map +0 -1
- package/lib/tree/util/regex.js +0 -4
- package/lib/tree/util/regex.js.map +0 -1
- package/lib/tree/util/registry-utils.d.ts +0 -192
- package/lib/tree/util/registry-utils.d.ts.map +0 -1
- package/lib/tree/util/registry-utils.js +0 -1214
- package/lib/tree/util/registry-utils.js.map +0 -1
- package/lib/tree/util/ruleset-trace.d.ts +0 -4
- package/lib/tree/util/ruleset-trace.d.ts.map +0 -1
- package/lib/tree/util/ruleset-trace.js +0 -14
- package/lib/tree/util/ruleset-trace.js.map +0 -1
- package/lib/tree/util/selector-compare.d.ts +0 -2
- package/lib/tree/util/selector-compare.d.ts.map +0 -1
- package/lib/tree/util/selector-compare.js +0 -2
- package/lib/tree/util/selector-compare.js.map +0 -1
- package/lib/tree/util/selector-match-core.d.ts +0 -184
- package/lib/tree/util/selector-match-core.d.ts.map +0 -1
- package/lib/tree/util/selector-match-core.js +0 -1603
- package/lib/tree/util/selector-match-core.js.map +0 -1
- package/lib/tree/util/selector-utils.d.ts +0 -30
- package/lib/tree/util/selector-utils.d.ts.map +0 -1
- package/lib/tree/util/selector-utils.js +0 -100
- package/lib/tree/util/selector-utils.js.map +0 -1
- package/lib/tree/util/serialize-helper.d.ts +0 -13
- package/lib/tree/util/serialize-helper.d.ts.map +0 -1
- package/lib/tree/util/serialize-helper.js +0 -387
- package/lib/tree/util/serialize-helper.js.map +0 -1
- package/lib/tree/util/serialize-types.d.ts +0 -9
- package/lib/tree/util/serialize-types.d.ts.map +0 -1
- package/lib/tree/util/serialize-types.js +0 -216
- package/lib/tree/util/serialize-types.js.map +0 -1
- package/lib/tree/util/should-operate.d.ts +0 -23
- package/lib/tree/util/should-operate.d.ts.map +0 -1
- package/lib/tree/util/should-operate.js +0 -46
- package/lib/tree/util/should-operate.js.map +0 -1
- package/lib/tree/util/sourcemap.d.ts +0 -7
- package/lib/tree/util/sourcemap.d.ts.map +0 -1
- package/lib/tree/util/sourcemap.js +0 -25
- package/lib/tree/util/sourcemap.js.map +0 -1
- package/lib/types/config.d.ts +0 -205
- package/lib/types/config.d.ts.map +0 -1
- package/lib/types/config.js +0 -2
- package/lib/types/config.js.map +0 -1
- package/lib/types/index.d.ts +0 -15
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/index.js +0 -3
- package/lib/types/index.js.map +0 -1
- package/lib/types/modes.d.ts.map +0 -1
- package/lib/types/modes.js +0 -2
- package/lib/types/modes.js.map +0 -1
- package/lib/types.d.ts +0 -61
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -2
- package/lib/types.js.map +0 -1
- package/lib/use-webpack-resolver.d.ts +0 -9
- package/lib/use-webpack-resolver.d.ts.map +0 -1
- package/lib/use-webpack-resolver.js +0 -41
- package/lib/use-webpack-resolver.js.map +0 -1
- package/lib/visitor/index.d.ts +0 -136
- package/lib/visitor/index.d.ts.map +0 -1
- package/lib/visitor/index.js +0 -135
- package/lib/visitor/index.js.map +0 -1
- package/lib/visitor/less-visitor.d.ts +0 -7
- package/lib/visitor/less-visitor.d.ts.map +0 -1
- package/lib/visitor/less-visitor.js.map +0 -1
|
@@ -0,0 +1,2171 @@
|
|
|
1
|
+
import { mixin, rules, el, decl, any, condition, expr, ref, list, vardecl, Node, Rules, call, ruleset, Ruleset, rest, sel, co, compound, atrule, interpolated, interpolatedSelector, nil, num, dimension, seq, amp, sellist, defaultguard } from '../index.js';
|
|
2
|
+
import { Context } from '../../context.js';
|
|
3
|
+
import { getFunctionFromMixins } from '../rules.js';
|
|
4
|
+
import { isVisibleInContext } from '../node-base.js';
|
|
5
|
+
import { getParent, getSourceParent, setParent, setSourceParent } from '../util/field-helpers.js';
|
|
6
|
+
|
|
7
|
+
let context: Context;
|
|
8
|
+
|
|
9
|
+
// Helper to check for errors without serializing the resolved value
|
|
10
|
+
async function expectRejects<T>(
|
|
11
|
+
promiseOrValue: Promise<T> | T,
|
|
12
|
+
ErrorType?: new (...args: any[]) => Error,
|
|
13
|
+
messagePattern?: RegExp
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
let error: unknown;
|
|
16
|
+
try {
|
|
17
|
+
await Promise.resolve(promiseOrValue);
|
|
18
|
+
// Create error that will point to the call site
|
|
19
|
+
const err = new Error('Expected promise to reject, but it resolved');
|
|
20
|
+
// Remove this function from the stack trace so it points to the call site
|
|
21
|
+
if (Error.captureStackTrace) {
|
|
22
|
+
Error.captureStackTrace(err, expectRejects);
|
|
23
|
+
} else {
|
|
24
|
+
// Fallback: remove the first line (this function) from stack
|
|
25
|
+
const stack = err.stack?.split('\n');
|
|
26
|
+
if (stack && stack.length > 1) {
|
|
27
|
+
err.stack = stack.slice(1).join('\n');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
throw err;
|
|
31
|
+
} catch (e) {
|
|
32
|
+
if (e instanceof Error && e.message === 'Expected promise to reject, but it resolved') {
|
|
33
|
+
throw e;
|
|
34
|
+
}
|
|
35
|
+
error = e;
|
|
36
|
+
}
|
|
37
|
+
if (!error) {
|
|
38
|
+
const err = new Error('Expected promise to reject, but it resolved');
|
|
39
|
+
if (Error.captureStackTrace) {
|
|
40
|
+
Error.captureStackTrace(err, expectRejects);
|
|
41
|
+
}
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
if (ErrorType && !(error instanceof ErrorType)) {
|
|
45
|
+
const errorName = error instanceof Error ? error.constructor.name : 'unknown';
|
|
46
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
47
|
+
const err = new Error(`Expected error to be instance of ${ErrorType.name}, but got ${errorName}: ${errorMsg}`);
|
|
48
|
+
if (Error.captureStackTrace) {
|
|
49
|
+
Error.captureStackTrace(err, expectRejects);
|
|
50
|
+
}
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
if (messagePattern) {
|
|
54
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
55
|
+
if (!messagePattern.test(errorMsg)) {
|
|
56
|
+
const err = new Error(`Expected error message to match ${messagePattern}, but got: ${errorMsg}`);
|
|
57
|
+
if (Error.captureStackTrace) {
|
|
58
|
+
Error.captureStackTrace(err, expectRejects);
|
|
59
|
+
}
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe('Mixin', () => {
|
|
66
|
+
afterAll(() => {
|
|
67
|
+
Node.prototype.fullRender = false;
|
|
68
|
+
});
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
Node.prototype.fullRender = true;
|
|
71
|
+
context = new Context({
|
|
72
|
+
/** This is the default Less behavior */
|
|
73
|
+
leakyRules: true
|
|
74
|
+
});
|
|
75
|
+
context.depth = 2;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('calling', () => {
|
|
79
|
+
it('should call a simple mixin', async () => {
|
|
80
|
+
// Create a mixin definition: .my-mixin() { color: red; }
|
|
81
|
+
const mixinDef = mixin({
|
|
82
|
+
name: any('.my-mixin'),
|
|
83
|
+
rules: rules([
|
|
84
|
+
decl({ name: 'color', value: any('red') })
|
|
85
|
+
])
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Create a ruleset that calls the mixin: .test { .my-mixin(); }
|
|
89
|
+
const testRuleset = ruleset({
|
|
90
|
+
selector: el('.test'),
|
|
91
|
+
rules: rules([
|
|
92
|
+
call({ name: ref({ key: '.my-mixin' }, { type: 'mixin' }) })
|
|
93
|
+
])
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Create root rules containing both
|
|
97
|
+
const root = rules([mixinDef, testRuleset]);
|
|
98
|
+
context.root = root;
|
|
99
|
+
|
|
100
|
+
const evald = await root.eval(context);
|
|
101
|
+
const css = evald.render(context);
|
|
102
|
+
|
|
103
|
+
expect(css).toBeString(`
|
|
104
|
+
.test {
|
|
105
|
+
color: red;
|
|
106
|
+
}
|
|
107
|
+
`);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should call a ruleset as a mixin (no parens)', async () => {
|
|
111
|
+
// Create a ruleset that can be used as a mixin: .my-mixin { color: red; }
|
|
112
|
+
const mixinRuleset = ruleset({
|
|
113
|
+
selector: el('.my-mixin'),
|
|
114
|
+
rules: rules([
|
|
115
|
+
decl({ name: 'color', value: any('red') })
|
|
116
|
+
])
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Create a ruleset that calls the mixin: .test { .my-mixin(); }
|
|
120
|
+
const testRuleset = ruleset({
|
|
121
|
+
selector: el('.test'),
|
|
122
|
+
rules: rules([
|
|
123
|
+
call({ name: ref({ key: '.my-mixin' }, { type: 'mixin-ruleset' }) }) // Use 'mixin-ruleset' to find both Mixins and Rulesets
|
|
124
|
+
])
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Create root rules containing both
|
|
128
|
+
const root = rules([mixinRuleset, testRuleset]);
|
|
129
|
+
context.root = root;
|
|
130
|
+
|
|
131
|
+
const evald = await root.eval(context);
|
|
132
|
+
const css = evald.render(context);
|
|
133
|
+
|
|
134
|
+
expect(css).toBeString(`
|
|
135
|
+
.my-mixin {
|
|
136
|
+
color: red;
|
|
137
|
+
}
|
|
138
|
+
.test {
|
|
139
|
+
color: red;
|
|
140
|
+
}
|
|
141
|
+
`);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should call a mixin with parameters', async () => {
|
|
145
|
+
// Create a mixin with a parameter: .my-mixin(@color) { color: @color; }
|
|
146
|
+
const mixinDef = mixin({
|
|
147
|
+
name: any('.my-mixin'),
|
|
148
|
+
params: list([
|
|
149
|
+
any('color', { role: 'property' }) // Parameter without default is Any with role: 'property' (like variable names)
|
|
150
|
+
]),
|
|
151
|
+
rules: rules([
|
|
152
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
|
|
153
|
+
])
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Create a ruleset that calls the mixin: .test { .my-mixin(blue); }
|
|
157
|
+
const testRuleset = ruleset({
|
|
158
|
+
selector: el('.test'),
|
|
159
|
+
rules: rules([
|
|
160
|
+
call({
|
|
161
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
162
|
+
args: list([any('blue')])
|
|
163
|
+
})
|
|
164
|
+
])
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Create root rules containing both
|
|
168
|
+
const root = rules([mixinDef, testRuleset]);
|
|
169
|
+
context.root = root;
|
|
170
|
+
|
|
171
|
+
const evald = await root.eval(context);
|
|
172
|
+
const css = evald.render(context);
|
|
173
|
+
|
|
174
|
+
expect(css).toBeString(`
|
|
175
|
+
.test {
|
|
176
|
+
color: blue;
|
|
177
|
+
}
|
|
178
|
+
`);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Removed: interpolated mixin names are resolved during eval before registration.
|
|
182
|
+
// A mixin with an unresolved interpolated name would never be in the registry.
|
|
183
|
+
|
|
184
|
+
it('should call a mixin with default parameter values', async () => {
|
|
185
|
+
// Create a mixin with a default parameter: .my-mixin(@color: red) { color: @color; }
|
|
186
|
+
const mixinDef = mixin({
|
|
187
|
+
name: any('.my-mixin'),
|
|
188
|
+
params: list([
|
|
189
|
+
vardecl({ name: 'color', value: any('red') }, { paramVar: true })
|
|
190
|
+
]),
|
|
191
|
+
rules: rules([
|
|
192
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
|
|
193
|
+
])
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Create a ruleset that calls the mixin without args: .test { .my-mixin(); }
|
|
197
|
+
const testRuleset1 = ruleset({
|
|
198
|
+
selector: el('.test1'),
|
|
199
|
+
rules: rules([
|
|
200
|
+
call({ name: ref({ key: '.my-mixin' }, { type: 'mixin' }) })
|
|
201
|
+
])
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Create a ruleset that calls the mixin with args: .test2 { .my-mixin(blue); }
|
|
205
|
+
const testRuleset2 = ruleset({
|
|
206
|
+
selector: el('.test2'),
|
|
207
|
+
rules: rules([
|
|
208
|
+
call({
|
|
209
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
210
|
+
args: list([any('blue')])
|
|
211
|
+
})
|
|
212
|
+
])
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const root = rules([mixinDef, testRuleset1, testRuleset2]);
|
|
216
|
+
context.root = root;
|
|
217
|
+
|
|
218
|
+
const evald = await root.eval(context);
|
|
219
|
+
// Position-backed trees require context for serialization
|
|
220
|
+
const css = evald.render(context);
|
|
221
|
+
|
|
222
|
+
expect(css).toBeString(`
|
|
223
|
+
.test1 {
|
|
224
|
+
color: red;
|
|
225
|
+
}
|
|
226
|
+
.test2 {
|
|
227
|
+
color: blue;
|
|
228
|
+
}
|
|
229
|
+
`);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should call a mixin with multiple parameters', async () => {
|
|
233
|
+
// Create a mixin with multiple parameters: .my-mixin(@color, @size) { color: @color; font-size: @size; }
|
|
234
|
+
const mixinDef = mixin({
|
|
235
|
+
name: any('.my-mixin'),
|
|
236
|
+
params: list([
|
|
237
|
+
any('color', { role: 'property' }),
|
|
238
|
+
any('size', { role: 'property' })
|
|
239
|
+
]),
|
|
240
|
+
rules: rules([
|
|
241
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) }),
|
|
242
|
+
decl({ name: 'font-size', value: ref({ key: 'size' }, { type: 'variable' }) })
|
|
243
|
+
])
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Create a ruleset that calls the mixin: .test { .my-mixin(blue, 16px); }
|
|
247
|
+
const testRuleset = ruleset({
|
|
248
|
+
selector: el('.test'),
|
|
249
|
+
rules: rules([
|
|
250
|
+
call({
|
|
251
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
252
|
+
args: list([any('blue'), any('16px')])
|
|
253
|
+
})
|
|
254
|
+
])
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const root = rules([mixinDef, testRuleset]);
|
|
258
|
+
context.root = root;
|
|
259
|
+
|
|
260
|
+
const evald = await root.eval(context);
|
|
261
|
+
const css = evald.render(context);
|
|
262
|
+
|
|
263
|
+
expect(css).toBeString(`
|
|
264
|
+
.test {
|
|
265
|
+
color: blue;
|
|
266
|
+
font-size: 16px;
|
|
267
|
+
}
|
|
268
|
+
`);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should call a mixin with @arguments', async () => {
|
|
272
|
+
// Create a mixin that uses @arguments: .my-mixin(@a, @b) { margin: @arguments; }
|
|
273
|
+
const mixinDef = mixin({
|
|
274
|
+
name: any('.my-mixin'),
|
|
275
|
+
params: list([
|
|
276
|
+
any('a', { role: 'property' }),
|
|
277
|
+
any('b', { role: 'property' })
|
|
278
|
+
]),
|
|
279
|
+
rules: rules([
|
|
280
|
+
decl({ name: 'margin', value: ref({ key: 'arguments' }, { type: 'variable' }) })
|
|
281
|
+
])
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Use a real dimension node plus a unitless number so @arguments spacing
|
|
285
|
+
// is proven on actual numeric nodes.
|
|
286
|
+
const testRuleset = ruleset({
|
|
287
|
+
selector: el('.test'),
|
|
288
|
+
rules: rules([
|
|
289
|
+
call({
|
|
290
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
291
|
+
args: list([dimension([10, 'px']), num(20)])
|
|
292
|
+
})
|
|
293
|
+
])
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const root = rules([mixinDef, testRuleset]);
|
|
297
|
+
context.root = root;
|
|
298
|
+
|
|
299
|
+
const evald = await root.eval(context);
|
|
300
|
+
const css = evald.render(context);
|
|
301
|
+
|
|
302
|
+
expect(css).toBeString(`
|
|
303
|
+
.test {
|
|
304
|
+
margin: 10px 20;
|
|
305
|
+
}
|
|
306
|
+
`);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('preEval sets private rulesVisibility on mixin body', async () => {
|
|
310
|
+
const localContext = new Context({ leakyRules: false });
|
|
311
|
+
localContext.depth = 2;
|
|
312
|
+
|
|
313
|
+
const mixinDef = mixin({
|
|
314
|
+
name: any('.my-mixin'),
|
|
315
|
+
rules: rules([
|
|
316
|
+
decl({ name: 'color', value: any('red') })
|
|
317
|
+
])
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const preEvald = await mixinDef.preEval(localContext);
|
|
321
|
+
|
|
322
|
+
const preEvaldRules = preEvald.get('rules');
|
|
323
|
+
expect(preEvaldRules.options.rulesVisibility.Mixin).toBe('private');
|
|
324
|
+
expect(preEvaldRules.options.rulesVisibility.VarDeclaration).toBe('private');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('should call a mixin with a guard condition', async () => {
|
|
328
|
+
// Create a mixin with a guard: .my-mixin(@color) when (@color = red) { color: @color; }
|
|
329
|
+
const mixinDef = mixin({
|
|
330
|
+
name: any('.my-mixin'),
|
|
331
|
+
params: list([
|
|
332
|
+
any('color', { role: 'property' })
|
|
333
|
+
]),
|
|
334
|
+
guard: condition([
|
|
335
|
+
expr(ref({ key: 'color' }, { type: 'variable' })),
|
|
336
|
+
'=',
|
|
337
|
+
any('red')
|
|
338
|
+
]),
|
|
339
|
+
rules: rules([
|
|
340
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
|
|
341
|
+
])
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Create a ruleset that calls the mixin with matching condition: .test1 { .my-mixin(red); }
|
|
345
|
+
const testRuleset1 = ruleset({
|
|
346
|
+
selector: el('.test1'),
|
|
347
|
+
rules: rules([
|
|
348
|
+
call({
|
|
349
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
350
|
+
args: list([any('red')])
|
|
351
|
+
})
|
|
352
|
+
])
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Create a ruleset that calls the mixin with non-matching condition: .test2 { .my-mixin(blue); }
|
|
356
|
+
const testRuleset2 = ruleset({
|
|
357
|
+
selector: el('.test2'),
|
|
358
|
+
rules: rules([
|
|
359
|
+
call({
|
|
360
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
361
|
+
args: list([any('blue')])
|
|
362
|
+
})
|
|
363
|
+
])
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const root = rules([mixinDef, testRuleset1, testRuleset2]);
|
|
367
|
+
context.root = root;
|
|
368
|
+
|
|
369
|
+
const evald = await root.eval(context);
|
|
370
|
+
const css = evald.render(context);
|
|
371
|
+
|
|
372
|
+
expect(css).toBeString(`
|
|
373
|
+
.test1 {
|
|
374
|
+
color: red;
|
|
375
|
+
}
|
|
376
|
+
`);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('replays default() guard candidates with bound params intact', async () => {
|
|
380
|
+
const exactMixin = mixin({
|
|
381
|
+
name: any('.my-mixin'),
|
|
382
|
+
params: list([
|
|
383
|
+
any('color', { role: 'property' })
|
|
384
|
+
]),
|
|
385
|
+
guard: condition([
|
|
386
|
+
expr(ref({ key: 'color' }, { type: 'variable' })),
|
|
387
|
+
'=',
|
|
388
|
+
any('red')
|
|
389
|
+
]),
|
|
390
|
+
rules: rules([
|
|
391
|
+
decl({ name: 'color', value: any('exact') })
|
|
392
|
+
])
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
const defaultMixin = mixin({
|
|
396
|
+
name: any('.my-mixin'),
|
|
397
|
+
params: list([
|
|
398
|
+
any('color', { role: 'property' })
|
|
399
|
+
]),
|
|
400
|
+
guard: condition([defaultguard('default()')]),
|
|
401
|
+
rules: rules([
|
|
402
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
|
|
403
|
+
])
|
|
404
|
+
}, { hasDefault: true });
|
|
405
|
+
|
|
406
|
+
const exactCall = ruleset({
|
|
407
|
+
selector: el('.exact'),
|
|
408
|
+
rules: rules([
|
|
409
|
+
call({
|
|
410
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
411
|
+
args: list([any('red')])
|
|
412
|
+
})
|
|
413
|
+
])
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
const fallbackCall = ruleset({
|
|
417
|
+
selector: el('.fallback'),
|
|
418
|
+
rules: rules([
|
|
419
|
+
call({
|
|
420
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
421
|
+
args: list([any('blue')])
|
|
422
|
+
})
|
|
423
|
+
])
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const root = rules([exactMixin, defaultMixin, exactCall, fallbackCall]);
|
|
427
|
+
context.root = root;
|
|
428
|
+
|
|
429
|
+
const evald = await root.eval(context);
|
|
430
|
+
const css = evald.render(context);
|
|
431
|
+
|
|
432
|
+
expect(css).toBeString(`
|
|
433
|
+
.exact {
|
|
434
|
+
color: exact;
|
|
435
|
+
}
|
|
436
|
+
.fallback {
|
|
437
|
+
color: blue;
|
|
438
|
+
}
|
|
439
|
+
`);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('guard params resolve and output renders correctly with position isolation', async () => {
|
|
443
|
+
const mixinDef = mixin({
|
|
444
|
+
name: any('.my-mixin'),
|
|
445
|
+
params: list([
|
|
446
|
+
any('color', { role: 'property' })
|
|
447
|
+
]),
|
|
448
|
+
guard: condition([
|
|
449
|
+
expr(ref({ key: 'color' }, { type: 'variable' })),
|
|
450
|
+
'=',
|
|
451
|
+
any('red')
|
|
452
|
+
]),
|
|
453
|
+
rules: rules([
|
|
454
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
|
|
455
|
+
])
|
|
456
|
+
});
|
|
457
|
+
const testRuleset = ruleset({
|
|
458
|
+
selector: el('.test'),
|
|
459
|
+
rules: rules([
|
|
460
|
+
call({
|
|
461
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
462
|
+
args: list([any('red')])
|
|
463
|
+
})
|
|
464
|
+
])
|
|
465
|
+
});
|
|
466
|
+
const root = rules([mixinDef, testRuleset]);
|
|
467
|
+
context.root = root;
|
|
468
|
+
|
|
469
|
+
const evald = await root.eval(context);
|
|
470
|
+
const css = evald.render(context);
|
|
471
|
+
|
|
472
|
+
expect(css).toBeString(`
|
|
473
|
+
.test {
|
|
474
|
+
color: red;
|
|
475
|
+
}
|
|
476
|
+
`);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('blocks a mixin candidate when its failed guard ancestor exists only in the state parent chain', async () => {
|
|
480
|
+
const mixinDef = mixin({
|
|
481
|
+
name: any('.my-mixin'),
|
|
482
|
+
rules: rules([
|
|
483
|
+
decl({ name: 'color', value: any('red') })
|
|
484
|
+
])
|
|
485
|
+
});
|
|
486
|
+
const failedAncestor = ruleset({
|
|
487
|
+
selector: el('.blocked'),
|
|
488
|
+
guard: nil(),
|
|
489
|
+
rules: rules([])
|
|
490
|
+
});
|
|
491
|
+
setParent(mixinDef, failedAncestor, context);
|
|
492
|
+
|
|
493
|
+
const fn = getFunctionFromMixins(mixinDef);
|
|
494
|
+
|
|
495
|
+
await expectRejects(fn.call(context), ReferenceError, /No matching mixins found/);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('evaluates mixin args against a caller source scope that exists only in the state chain', async () => {
|
|
499
|
+
const mixinDef = mixin({
|
|
500
|
+
name: any('.my-mixin'),
|
|
501
|
+
params: list([
|
|
502
|
+
any('color', { role: 'property' })
|
|
503
|
+
]),
|
|
504
|
+
rules: rules([
|
|
505
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
|
|
506
|
+
])
|
|
507
|
+
});
|
|
508
|
+
const mixinRoot = rules([mixinDef]);
|
|
509
|
+
context.root = mixinRoot;
|
|
510
|
+
|
|
511
|
+
const sourceAnchor = decl({ name: 'background', value: any('white') });
|
|
512
|
+
const sourceRules = rules([
|
|
513
|
+
vardecl({ name: 'theme', value: any('blue') }),
|
|
514
|
+
sourceAnchor
|
|
515
|
+
]);
|
|
516
|
+
const caller = call({
|
|
517
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
518
|
+
args: list([ref({ key: 'theme' }, { type: 'variable' })])
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
setSourceParent((caller as any).name, sourceAnchor, context);
|
|
522
|
+
context.caller = caller;
|
|
523
|
+
|
|
524
|
+
const fn = getFunctionFromMixins(mixinDef);
|
|
525
|
+
const result = await fn.call(context, ref({ key: 'theme' }, { type: 'variable' }));
|
|
526
|
+
|
|
527
|
+
expect(result.render(context)).toBeString(`
|
|
528
|
+
color: blue;
|
|
529
|
+
`);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('mixin with parameters renders correctly via eval', async () => {
|
|
533
|
+
const mixinDef = mixin({
|
|
534
|
+
name: any('.my-mixin'),
|
|
535
|
+
params: list([
|
|
536
|
+
any('color', { role: 'property' })
|
|
537
|
+
]),
|
|
538
|
+
rules: rules([
|
|
539
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
|
|
540
|
+
])
|
|
541
|
+
});
|
|
542
|
+
const testRuleset = ruleset({
|
|
543
|
+
selector: el('.test'),
|
|
544
|
+
rules: rules([
|
|
545
|
+
call({
|
|
546
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
547
|
+
args: list([any('blue')])
|
|
548
|
+
})
|
|
549
|
+
])
|
|
550
|
+
});
|
|
551
|
+
const root = rules([mixinDef, testRuleset]);
|
|
552
|
+
context.root = root;
|
|
553
|
+
|
|
554
|
+
const evald = await root.eval(context);
|
|
555
|
+
const css = evald.render(context);
|
|
556
|
+
|
|
557
|
+
expect(css).toBeString(`
|
|
558
|
+
.test {
|
|
559
|
+
color: blue;
|
|
560
|
+
}
|
|
561
|
+
`);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('mixin with bound parameters renders values from call site', async () => {
|
|
565
|
+
const mixinDef = mixin({
|
|
566
|
+
name: any('.my-mixin'),
|
|
567
|
+
params: list([
|
|
568
|
+
any('color', { role: 'property' })
|
|
569
|
+
]),
|
|
570
|
+
rules: rules([
|
|
571
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
|
|
572
|
+
])
|
|
573
|
+
});
|
|
574
|
+
const testRuleset = ruleset({
|
|
575
|
+
selector: el('.test'),
|
|
576
|
+
rules: rules([
|
|
577
|
+
call({
|
|
578
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
579
|
+
args: list([any('blue')])
|
|
580
|
+
})
|
|
581
|
+
])
|
|
582
|
+
});
|
|
583
|
+
const root = rules([mixinDef, testRuleset]);
|
|
584
|
+
context.root = root;
|
|
585
|
+
|
|
586
|
+
const evald = await root.eval(context);
|
|
587
|
+
const css = evald.render(context);
|
|
588
|
+
|
|
589
|
+
expect(css).toBeString(`
|
|
590
|
+
.test {
|
|
591
|
+
color: blue;
|
|
592
|
+
}
|
|
593
|
+
`);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('mixin with nested ruleset renders with position-resolved values', async () => {
|
|
597
|
+
const mixinDef = mixin({
|
|
598
|
+
name: any('.my-mixin'),
|
|
599
|
+
params: list([
|
|
600
|
+
any('color', { role: 'property' })
|
|
601
|
+
]),
|
|
602
|
+
rules: rules([
|
|
603
|
+
ruleset({
|
|
604
|
+
selector: el('.inner'),
|
|
605
|
+
rules: rules([
|
|
606
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
|
|
607
|
+
])
|
|
608
|
+
})
|
|
609
|
+
])
|
|
610
|
+
});
|
|
611
|
+
const testRuleset = ruleset({
|
|
612
|
+
selector: el('.test'),
|
|
613
|
+
rules: rules([
|
|
614
|
+
call({
|
|
615
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
616
|
+
args: list([any('blue')])
|
|
617
|
+
})
|
|
618
|
+
])
|
|
619
|
+
});
|
|
620
|
+
const root = rules([mixinDef, testRuleset]);
|
|
621
|
+
context.root = root;
|
|
622
|
+
|
|
623
|
+
const evald = await root.eval(context);
|
|
624
|
+
const css = evald.render(context);
|
|
625
|
+
|
|
626
|
+
expect(css).toBeString(`
|
|
627
|
+
.test {
|
|
628
|
+
.inner {
|
|
629
|
+
color: blue;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
`);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it('mixin with declarations and nested rulesets resolves params through position', async () => {
|
|
636
|
+
const mixinDef = mixin({
|
|
637
|
+
name: any('.my-mixin'),
|
|
638
|
+
params: list([
|
|
639
|
+
any('color', { role: 'property' })
|
|
640
|
+
]),
|
|
641
|
+
rules: rules([
|
|
642
|
+
decl({ name: 'background', value: ref({ key: 'color' }, { type: 'variable' }) }),
|
|
643
|
+
ruleset({
|
|
644
|
+
selector: el('.inner'),
|
|
645
|
+
rules: rules([
|
|
646
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
|
|
647
|
+
])
|
|
648
|
+
})
|
|
649
|
+
])
|
|
650
|
+
});
|
|
651
|
+
const testRuleset = ruleset({
|
|
652
|
+
selector: el('.test'),
|
|
653
|
+
rules: rules([
|
|
654
|
+
call({
|
|
655
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
656
|
+
args: list([any('blue')])
|
|
657
|
+
})
|
|
658
|
+
])
|
|
659
|
+
});
|
|
660
|
+
const root = rules([mixinDef, testRuleset]);
|
|
661
|
+
context.root = root;
|
|
662
|
+
|
|
663
|
+
const evald = await root.eval(context);
|
|
664
|
+
const css = evald.render(context);
|
|
665
|
+
|
|
666
|
+
expect(css).toBeString(`
|
|
667
|
+
.test {
|
|
668
|
+
background: blue;
|
|
669
|
+
.inner {
|
|
670
|
+
color: blue;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
`);
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
it('multi-candidate mixin renders output from all matching candidates', async () => {
|
|
677
|
+
const mixinA = mixin({
|
|
678
|
+
name: any('.my-mixin'),
|
|
679
|
+
rules: rules([
|
|
680
|
+
decl({ name: 'color', value: any('red') })
|
|
681
|
+
])
|
|
682
|
+
});
|
|
683
|
+
const mixinB = mixin({
|
|
684
|
+
name: any('.my-mixin'),
|
|
685
|
+
rules: rules([
|
|
686
|
+
ruleset({
|
|
687
|
+
selector: el('.inner'),
|
|
688
|
+
rules: rules([
|
|
689
|
+
decl({ name: 'background', value: any('blue') })
|
|
690
|
+
])
|
|
691
|
+
})
|
|
692
|
+
])
|
|
693
|
+
});
|
|
694
|
+
const testRuleset = ruleset({
|
|
695
|
+
selector: el('.test'),
|
|
696
|
+
rules: rules([
|
|
697
|
+
call({
|
|
698
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' })
|
|
699
|
+
})
|
|
700
|
+
])
|
|
701
|
+
});
|
|
702
|
+
const root = rules([mixinA, mixinB, testRuleset]);
|
|
703
|
+
context.root = root;
|
|
704
|
+
|
|
705
|
+
const evald = await root.eval(context);
|
|
706
|
+
const css = evald.render(context);
|
|
707
|
+
|
|
708
|
+
expect(css).toBeString(`
|
|
709
|
+
.test {
|
|
710
|
+
color: red;
|
|
711
|
+
.inner {
|
|
712
|
+
background: blue;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
`);
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
it('should call a mixin that calls another mixin', async () => {
|
|
719
|
+
// Create a base mixin: .base-mixin(@color) { color: @color; }
|
|
720
|
+
const baseMixin = mixin({
|
|
721
|
+
name: any('.base-mixin'),
|
|
722
|
+
params: list([
|
|
723
|
+
any('color', { role: 'property' })
|
|
724
|
+
]),
|
|
725
|
+
rules: rules([
|
|
726
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
|
|
727
|
+
])
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
// Create a mixin that calls the base mixin: .wrapper-mixin(@color) { .base-mixin(@color); }
|
|
731
|
+
const wrapperMixin = mixin({
|
|
732
|
+
name: any('.wrapper-mixin'),
|
|
733
|
+
params: list([
|
|
734
|
+
any('color', { role: 'property' })
|
|
735
|
+
]),
|
|
736
|
+
rules: rules([
|
|
737
|
+
call({
|
|
738
|
+
name: ref({ key: '.base-mixin' }, { type: 'mixin' }),
|
|
739
|
+
args: list([ref({ key: 'color' }, { type: 'variable' })])
|
|
740
|
+
})
|
|
741
|
+
])
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// Create a ruleset that calls the wrapper mixin: .test { .wrapper-mixin(blue); }
|
|
745
|
+
const testRuleset = ruleset({
|
|
746
|
+
selector: el('.test'),
|
|
747
|
+
rules: rules([
|
|
748
|
+
call({
|
|
749
|
+
name: ref({ key: '.wrapper-mixin' }, { type: 'mixin' }),
|
|
750
|
+
args: list([any('blue')])
|
|
751
|
+
})
|
|
752
|
+
])
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
const root = rules([baseMixin, wrapperMixin, testRuleset]);
|
|
756
|
+
context.root = root;
|
|
757
|
+
|
|
758
|
+
const evald = await root.eval(context);
|
|
759
|
+
const css = evald.render(context);
|
|
760
|
+
|
|
761
|
+
expect(css).toBeString(`
|
|
762
|
+
.test {
|
|
763
|
+
color: blue;
|
|
764
|
+
}
|
|
765
|
+
`);
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
it('nested mixin calls render correctly through position pipeline', async () => {
|
|
769
|
+
const baseMixin = mixin({
|
|
770
|
+
name: any('.base-mixin'),
|
|
771
|
+
params: list([
|
|
772
|
+
any('color', { role: 'property' })
|
|
773
|
+
]),
|
|
774
|
+
rules: rules([
|
|
775
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
|
|
776
|
+
])
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
const wrapperMixin = mixin({
|
|
780
|
+
name: any('.wrapper-mixin'),
|
|
781
|
+
params: list([
|
|
782
|
+
any('color', { role: 'property' })
|
|
783
|
+
]),
|
|
784
|
+
rules: rules([
|
|
785
|
+
call({
|
|
786
|
+
name: ref({ key: '.base-mixin' }, { type: 'mixin' }),
|
|
787
|
+
args: list([ref({ key: 'color' }, { type: 'variable' })])
|
|
788
|
+
})
|
|
789
|
+
])
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
const testRuleset = ruleset({
|
|
793
|
+
selector: el('.test'),
|
|
794
|
+
rules: rules([
|
|
795
|
+
call({
|
|
796
|
+
name: ref({ key: '.wrapper-mixin' }, { type: 'mixin' }),
|
|
797
|
+
args: list([any('blue')])
|
|
798
|
+
})
|
|
799
|
+
])
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
const root = rules([baseMixin, wrapperMixin, testRuleset]);
|
|
803
|
+
context.root = root;
|
|
804
|
+
|
|
805
|
+
const evald = await root.eval(context);
|
|
806
|
+
const css = evald.render(context);
|
|
807
|
+
|
|
808
|
+
expect(css).toBeString(`
|
|
809
|
+
.test {
|
|
810
|
+
color: blue;
|
|
811
|
+
}
|
|
812
|
+
`);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it('keeps outer mixin params available to nested emitted mixins through ruleset closure', async () => {
|
|
816
|
+
const personMixin = mixin({
|
|
817
|
+
name: any('.Person'),
|
|
818
|
+
params: list([
|
|
819
|
+
vardecl({ name: 'name', value: nil() }, { paramVar: true }),
|
|
820
|
+
vardecl({ name: 'gender_', value: nil() }, { paramVar: true })
|
|
821
|
+
]),
|
|
822
|
+
rules: rules([
|
|
823
|
+
ruleset({
|
|
824
|
+
selector: sellist([
|
|
825
|
+
sel([
|
|
826
|
+
interpolatedSelector(interpolated({
|
|
827
|
+
source: '.%%',
|
|
828
|
+
replacements: [ref({ key: 'name' }, { type: 'variable' })]
|
|
829
|
+
}))
|
|
830
|
+
])
|
|
831
|
+
]),
|
|
832
|
+
rules: rules([
|
|
833
|
+
vardecl({
|
|
834
|
+
name: 'gender',
|
|
835
|
+
value: ref({ key: 'gender_' }, { type: 'variable' })
|
|
836
|
+
}),
|
|
837
|
+
mixin({
|
|
838
|
+
name: any('.sayGender'),
|
|
839
|
+
rules: rules([
|
|
840
|
+
decl({ name: 'gender', value: ref({ key: 'gender' }, { type: 'variable' }) })
|
|
841
|
+
])
|
|
842
|
+
})
|
|
843
|
+
])
|
|
844
|
+
})
|
|
845
|
+
])
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
const testRuleset = ruleset({
|
|
849
|
+
selector: el('mi-test-d'),
|
|
850
|
+
rules: rules([
|
|
851
|
+
call({
|
|
852
|
+
name: ref({ key: '.Person' }, { type: 'mixin' }),
|
|
853
|
+
args: list([any('person'), any('"Male"')])
|
|
854
|
+
}),
|
|
855
|
+
call({
|
|
856
|
+
name: ref({ key: ['.person', '.sayGender'] }, { type: 'mixin-ruleset' })
|
|
857
|
+
})
|
|
858
|
+
])
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
const root = rules([personMixin, testRuleset]);
|
|
862
|
+
context.root = root;
|
|
863
|
+
|
|
864
|
+
const evald = await root.eval(context);
|
|
865
|
+
const previousFullRender = Node.prototype.fullRender;
|
|
866
|
+
Node.prototype.fullRender = false;
|
|
867
|
+
let css: string;
|
|
868
|
+
try {
|
|
869
|
+
css = evald.render(context);
|
|
870
|
+
} finally {
|
|
871
|
+
Node.prototype.fullRender = previousFullRender;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
expect(css).toBeString(`
|
|
875
|
+
mi-test-d {
|
|
876
|
+
gender: "Male";
|
|
877
|
+
}
|
|
878
|
+
`);
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
it('keeps emitted namespace rules available for lookup without making them render-visible', async () => {
|
|
882
|
+
const personMixin = mixin({
|
|
883
|
+
name: any('.Person'),
|
|
884
|
+
params: list([
|
|
885
|
+
vardecl({ name: 'name', value: nil() }, { paramVar: true }),
|
|
886
|
+
vardecl({ name: 'gender_', value: nil() }, { paramVar: true })
|
|
887
|
+
]),
|
|
888
|
+
rules: rules([
|
|
889
|
+
ruleset({
|
|
890
|
+
selector: sellist([
|
|
891
|
+
sel([
|
|
892
|
+
interpolatedSelector(interpolated({
|
|
893
|
+
source: '.%%',
|
|
894
|
+
replacements: [ref({ key: 'name' }, { type: 'variable' })]
|
|
895
|
+
}))
|
|
896
|
+
])
|
|
897
|
+
]),
|
|
898
|
+
rules: rules([
|
|
899
|
+
vardecl({
|
|
900
|
+
name: 'gender',
|
|
901
|
+
value: ref({ key: 'gender_' }, { type: 'variable' })
|
|
902
|
+
}),
|
|
903
|
+
mixin({
|
|
904
|
+
name: any('.sayGender'),
|
|
905
|
+
rules: rules([
|
|
906
|
+
decl({ name: 'gender', value: ref({ key: 'gender' }, { type: 'variable' }) })
|
|
907
|
+
])
|
|
908
|
+
})
|
|
909
|
+
])
|
|
910
|
+
})
|
|
911
|
+
])
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
const firstCall = call({
|
|
915
|
+
name: ref({ key: '.Person' }, { type: 'mixin' }),
|
|
916
|
+
args: list([any('person'), any('"Male"')])
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
const root = rules([personMixin, firstCall]);
|
|
920
|
+
context.root = root;
|
|
921
|
+
|
|
922
|
+
const evald = await root.eval(context);
|
|
923
|
+
const output = evald.get('value')[1];
|
|
924
|
+
|
|
925
|
+
expect(output).toBeInstanceOf(Rules);
|
|
926
|
+
|
|
927
|
+
const outputRules = output as Rules;
|
|
928
|
+
const outputContext = {
|
|
929
|
+
...context,
|
|
930
|
+
renderKey: outputRules.renderKey,
|
|
931
|
+
rulesContext: outputRules
|
|
932
|
+
} as Context;
|
|
933
|
+
const children = outputRules.getRegistryChildren(outputContext);
|
|
934
|
+
expect(children).toHaveLength(1);
|
|
935
|
+
expect(children[0]).toBeInstanceOf(Ruleset);
|
|
936
|
+
|
|
937
|
+
const personRuleset = children[0] as Ruleset;
|
|
938
|
+
expect(personRuleset.valueOf()).toBe('.person');
|
|
939
|
+
|
|
940
|
+
const personScope = personRuleset.enterRules(outputContext);
|
|
941
|
+
const scopeChildren = personScope.getRegistryChildren(outputContext);
|
|
942
|
+
expect(scopeChildren.map(child => `${child.type}:${isVisibleInContext(child, outputContext)}`)).toEqual([
|
|
943
|
+
'VarDeclaration:false',
|
|
944
|
+
'Mixin:false'
|
|
945
|
+
]);
|
|
946
|
+
expect(isVisibleInContext(personRuleset, outputContext)).toBe(false);
|
|
947
|
+
expect(personScope.find('declaration', 'gender', 'VarDeclaration', { context: outputContext })).toBeDefined();
|
|
948
|
+
expect(personScope.find('mixin', '.sayGender', 'Mixin', { context: outputContext })).toBeDefined();
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
it('keeps namespaced mixin call output isolated in the parent rules before final render', async () => {
|
|
952
|
+
const personMixin = mixin({
|
|
953
|
+
name: any('.Person'),
|
|
954
|
+
params: list([
|
|
955
|
+
vardecl({ name: 'name', value: nil() }, { paramVar: true }),
|
|
956
|
+
vardecl({ name: 'gender_', value: nil() }, { paramVar: true })
|
|
957
|
+
]),
|
|
958
|
+
rules: rules([
|
|
959
|
+
ruleset({
|
|
960
|
+
selector: sellist([
|
|
961
|
+
sel([
|
|
962
|
+
interpolatedSelector(interpolated({
|
|
963
|
+
source: '.%%',
|
|
964
|
+
replacements: [ref({ key: 'name' }, { type: 'variable' })]
|
|
965
|
+
}))
|
|
966
|
+
])
|
|
967
|
+
]),
|
|
968
|
+
rules: rules([
|
|
969
|
+
vardecl({
|
|
970
|
+
name: 'gender',
|
|
971
|
+
value: ref({ key: 'gender_' }, { type: 'variable' })
|
|
972
|
+
}),
|
|
973
|
+
mixin({
|
|
974
|
+
name: any('.sayGender'),
|
|
975
|
+
rules: rules([
|
|
976
|
+
decl({ name: 'gender', value: ref({ key: 'gender' }, { type: 'variable' }) })
|
|
977
|
+
])
|
|
978
|
+
})
|
|
979
|
+
])
|
|
980
|
+
})
|
|
981
|
+
])
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
const host = ruleset({
|
|
985
|
+
selector: el('mi-test-d'),
|
|
986
|
+
rules: rules([
|
|
987
|
+
call({
|
|
988
|
+
name: ref({ key: '.Person' }, { type: 'mixin' }),
|
|
989
|
+
args: list([any('person'), any('"Male"')])
|
|
990
|
+
}),
|
|
991
|
+
call({
|
|
992
|
+
name: ref({ key: ['.person', '.sayGender'] }, { type: 'mixin-ruleset' })
|
|
993
|
+
})
|
|
994
|
+
])
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
const root = rules([personMixin, host]);
|
|
998
|
+
context.root = root;
|
|
999
|
+
|
|
1000
|
+
const evald = await root.eval(context);
|
|
1001
|
+
const hostRules = (evald.get('value')[1] as Ruleset).enterRules(context);
|
|
1002
|
+
const hostContext = {
|
|
1003
|
+
...context,
|
|
1004
|
+
renderKey: hostRules.renderKey,
|
|
1005
|
+
rulesContext: hostRules
|
|
1006
|
+
} as Context;
|
|
1007
|
+
|
|
1008
|
+
expect(
|
|
1009
|
+
hostRules.getRegistryChildren(hostContext).map(child => `${child.type}:${child.type === 'Rules' ? (child as Rules).getRegistryChildren(hostContext).map(inner => inner.type).join(',') : child.valueOf()}`)
|
|
1010
|
+
).toEqual([
|
|
1011
|
+
'Rules:Ruleset',
|
|
1012
|
+
'Rules:Declaration'
|
|
1013
|
+
]);
|
|
1014
|
+
|
|
1015
|
+
const firstCallOutput = hostRules.getRegistryChildren(hostContext)[0] as Rules;
|
|
1016
|
+
const namespacedRuleset = firstCallOutput.getRegistryChildren({
|
|
1017
|
+
...hostContext,
|
|
1018
|
+
renderKey: firstCallOutput.renderKey,
|
|
1019
|
+
rulesContext: firstCallOutput
|
|
1020
|
+
} as Context)[0] as Ruleset;
|
|
1021
|
+
expect(isVisibleInContext(namespacedRuleset, {
|
|
1022
|
+
...hostContext,
|
|
1023
|
+
renderKey: firstCallOutput.renderKey,
|
|
1024
|
+
rulesContext: firstCallOutput
|
|
1025
|
+
} as Context)).toBe(false);
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
it('keeps guard and default-param closure for emitted nested mixins', async () => {
|
|
1029
|
+
const lockMixin = mixin({
|
|
1030
|
+
name: any('.lock-mixin'),
|
|
1031
|
+
params: list([
|
|
1032
|
+
vardecl({ name: 'a', value: nil() }, { paramVar: true })
|
|
1033
|
+
]),
|
|
1034
|
+
rules: rules([
|
|
1035
|
+
mixin({
|
|
1036
|
+
name: any('.inner-locked-mixin'),
|
|
1037
|
+
params: list([
|
|
1038
|
+
vardecl({
|
|
1039
|
+
name: 'x',
|
|
1040
|
+
value: ref({ key: 'a' }, { type: 'variable' })
|
|
1041
|
+
}, { paramVar: true })
|
|
1042
|
+
]),
|
|
1043
|
+
guard: condition([
|
|
1044
|
+
expr(ref({ key: 'a' }, { type: 'variable' })),
|
|
1045
|
+
'=',
|
|
1046
|
+
num(1)
|
|
1047
|
+
]),
|
|
1048
|
+
rules: rules([
|
|
1049
|
+
decl({ name: 'a', value: ref({ key: 'a' }, { type: 'variable' }) }),
|
|
1050
|
+
decl({ name: 'x', value: ref({ key: 'x' }, { type: 'variable' }) })
|
|
1051
|
+
])
|
|
1052
|
+
})
|
|
1053
|
+
])
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
const host = ruleset({
|
|
1057
|
+
selector: el('.call-lock-mixin'),
|
|
1058
|
+
rules: rules([
|
|
1059
|
+
call({
|
|
1060
|
+
name: ref({ key: '.lock-mixin' }, { type: 'mixin' }),
|
|
1061
|
+
args: list([num(1)])
|
|
1062
|
+
}),
|
|
1063
|
+
ruleset({
|
|
1064
|
+
selector: el('.call-inner-lock-mixin'),
|
|
1065
|
+
rules: rules([
|
|
1066
|
+
call({
|
|
1067
|
+
name: ref({ key: '.inner-locked-mixin' }, { type: 'mixin' })
|
|
1068
|
+
})
|
|
1069
|
+
])
|
|
1070
|
+
})
|
|
1071
|
+
])
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
const localContext = new Context({
|
|
1075
|
+
leakyRules: true,
|
|
1076
|
+
collapseNesting: true
|
|
1077
|
+
});
|
|
1078
|
+
localContext.depth = 2;
|
|
1079
|
+
const root = rules([lockMixin, host]);
|
|
1080
|
+
localContext.root = root;
|
|
1081
|
+
|
|
1082
|
+
const evald = await root.eval(localContext);
|
|
1083
|
+
const previousFullRender = Node.prototype.fullRender;
|
|
1084
|
+
Node.prototype.fullRender = false;
|
|
1085
|
+
let css: string;
|
|
1086
|
+
try {
|
|
1087
|
+
css = evald.render(localContext);
|
|
1088
|
+
} finally {
|
|
1089
|
+
Node.prototype.fullRender = previousFullRender;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
expect(css).toBeString(`
|
|
1093
|
+
.call-lock-mixin .call-inner-lock-mixin {
|
|
1094
|
+
a: 1;
|
|
1095
|
+
x: 1;
|
|
1096
|
+
}
|
|
1097
|
+
`);
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
it('keeps emitted nested mixin closure ahead of same-named globals', async () => {
|
|
1101
|
+
const globalA = vardecl({ name: 'a', value: any('auto') });
|
|
1102
|
+
|
|
1103
|
+
const lockMixin = mixin({
|
|
1104
|
+
name: any('.lock-mixin'),
|
|
1105
|
+
params: list([
|
|
1106
|
+
vardecl({ name: 'a', value: nil() }, { paramVar: true })
|
|
1107
|
+
]),
|
|
1108
|
+
rules: rules([
|
|
1109
|
+
mixin({
|
|
1110
|
+
name: any('.inner-locked-mixin'),
|
|
1111
|
+
params: list([
|
|
1112
|
+
vardecl({
|
|
1113
|
+
name: 'x',
|
|
1114
|
+
value: ref({ key: 'a' }, { type: 'variable' })
|
|
1115
|
+
}, { paramVar: true })
|
|
1116
|
+
]),
|
|
1117
|
+
guard: condition([
|
|
1118
|
+
expr(ref({ key: 'a' }, { type: 'variable' })),
|
|
1119
|
+
'=',
|
|
1120
|
+
num(1)
|
|
1121
|
+
]),
|
|
1122
|
+
rules: rules([
|
|
1123
|
+
decl({ name: 'a', value: ref({ key: 'a' }, { type: 'variable' }) }),
|
|
1124
|
+
decl({ name: 'x', value: ref({ key: 'x' }, { type: 'variable' }) })
|
|
1125
|
+
])
|
|
1126
|
+
})
|
|
1127
|
+
])
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
const host = ruleset({
|
|
1131
|
+
selector: el('.call-lock-mixin'),
|
|
1132
|
+
rules: rules([
|
|
1133
|
+
call({
|
|
1134
|
+
name: ref({ key: '.lock-mixin' }, { type: 'mixin' }),
|
|
1135
|
+
args: list([num(1)])
|
|
1136
|
+
}),
|
|
1137
|
+
ruleset({
|
|
1138
|
+
selector: el('.call-inner-lock-mixin'),
|
|
1139
|
+
rules: rules([
|
|
1140
|
+
call({
|
|
1141
|
+
name: ref({ key: '.inner-locked-mixin' }, { type: 'mixin' })
|
|
1142
|
+
})
|
|
1143
|
+
])
|
|
1144
|
+
})
|
|
1145
|
+
])
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
const localContext = new Context({
|
|
1149
|
+
leakyRules: true,
|
|
1150
|
+
collapseNesting: true
|
|
1151
|
+
});
|
|
1152
|
+
localContext.depth = 2;
|
|
1153
|
+
const root = rules([globalA, lockMixin, host]);
|
|
1154
|
+
localContext.root = root;
|
|
1155
|
+
|
|
1156
|
+
const evald = await root.eval(localContext);
|
|
1157
|
+
const previousFullRender = Node.prototype.fullRender;
|
|
1158
|
+
Node.prototype.fullRender = false;
|
|
1159
|
+
let css: string;
|
|
1160
|
+
try {
|
|
1161
|
+
css = evald.render(localContext);
|
|
1162
|
+
} finally {
|
|
1163
|
+
Node.prototype.fullRender = previousFullRender;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
expect(css).toBeString(`
|
|
1167
|
+
.call-lock-mixin .call-inner-lock-mixin {
|
|
1168
|
+
a: 1;
|
|
1169
|
+
x: 1;
|
|
1170
|
+
}
|
|
1171
|
+
`);
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
it('still matches the emitted nested mixin guard ahead of same-named globals when args are explicit', async () => {
|
|
1175
|
+
const globalA = vardecl({ name: 'a', value: any('auto') });
|
|
1176
|
+
|
|
1177
|
+
const lockMixin = mixin({
|
|
1178
|
+
name: any('.lock-mixin'),
|
|
1179
|
+
params: list([
|
|
1180
|
+
vardecl({ name: 'a', value: nil() }, { paramVar: true })
|
|
1181
|
+
]),
|
|
1182
|
+
rules: rules([
|
|
1183
|
+
mixin({
|
|
1184
|
+
name: any('.inner-locked-mixin'),
|
|
1185
|
+
params: list([
|
|
1186
|
+
vardecl({
|
|
1187
|
+
name: 'x',
|
|
1188
|
+
value: ref({ key: 'a' }, { type: 'variable' })
|
|
1189
|
+
}, { paramVar: true })
|
|
1190
|
+
]),
|
|
1191
|
+
guard: condition([
|
|
1192
|
+
expr(ref({ key: 'a' }, { type: 'variable' })),
|
|
1193
|
+
'=',
|
|
1194
|
+
num(1)
|
|
1195
|
+
]),
|
|
1196
|
+
rules: rules([
|
|
1197
|
+
decl({ name: 'a', value: ref({ key: 'a' }, { type: 'variable' }) }),
|
|
1198
|
+
decl({ name: 'x', value: ref({ key: 'x' }, { type: 'variable' }) })
|
|
1199
|
+
])
|
|
1200
|
+
})
|
|
1201
|
+
])
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
const host = ruleset({
|
|
1205
|
+
selector: el('.call-lock-mixin'),
|
|
1206
|
+
rules: rules([
|
|
1207
|
+
call({
|
|
1208
|
+
name: ref({ key: '.lock-mixin' }, { type: 'mixin' }),
|
|
1209
|
+
args: list([num(1)])
|
|
1210
|
+
}),
|
|
1211
|
+
ruleset({
|
|
1212
|
+
selector: el('.call-inner-lock-mixin'),
|
|
1213
|
+
rules: rules([
|
|
1214
|
+
call({
|
|
1215
|
+
name: ref({ key: '.inner-locked-mixin' }, { type: 'mixin' }),
|
|
1216
|
+
args: list([num(1)])
|
|
1217
|
+
})
|
|
1218
|
+
])
|
|
1219
|
+
})
|
|
1220
|
+
])
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
const localContext = new Context({
|
|
1224
|
+
leakyRules: true,
|
|
1225
|
+
collapseNesting: true
|
|
1226
|
+
});
|
|
1227
|
+
localContext.depth = 2;
|
|
1228
|
+
const root = rules([globalA, lockMixin, host]);
|
|
1229
|
+
localContext.root = root;
|
|
1230
|
+
|
|
1231
|
+
const evald = await root.eval(localContext);
|
|
1232
|
+
const previousFullRender = Node.prototype.fullRender;
|
|
1233
|
+
Node.prototype.fullRender = false;
|
|
1234
|
+
let css: string;
|
|
1235
|
+
try {
|
|
1236
|
+
css = evald.render(localContext);
|
|
1237
|
+
} finally {
|
|
1238
|
+
Node.prototype.fullRender = previousFullRender;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
expect(css).toBeString(`
|
|
1242
|
+
.call-lock-mixin .call-inner-lock-mixin {
|
|
1243
|
+
a: 1;
|
|
1244
|
+
x: 1;
|
|
1245
|
+
}
|
|
1246
|
+
`);
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
it('should call a mixin with pattern matching by value', async () => {
|
|
1250
|
+
// Create mixins with pattern matching: .mixin(red) and .mixin(blue)
|
|
1251
|
+
const redMixin = mixin({
|
|
1252
|
+
name: any('.mixin'),
|
|
1253
|
+
params: list([
|
|
1254
|
+
any('red') // Pattern match - must be exactly 'red'
|
|
1255
|
+
]),
|
|
1256
|
+
rules: rules([
|
|
1257
|
+
decl({ name: 'color', value: any('red') })
|
|
1258
|
+
])
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
const blueMixin = mixin({
|
|
1262
|
+
name: any('.mixin'),
|
|
1263
|
+
params: list([
|
|
1264
|
+
any('blue') // Pattern match - must be exactly 'blue'
|
|
1265
|
+
]),
|
|
1266
|
+
rules: rules([
|
|
1267
|
+
decl({ name: 'color', value: any('blue') })
|
|
1268
|
+
])
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
// Create rulesets that call the mixin with different values
|
|
1272
|
+
const testRuleset1 = ruleset({
|
|
1273
|
+
selector: el('.test1'),
|
|
1274
|
+
rules: rules([
|
|
1275
|
+
call({
|
|
1276
|
+
name: ref({ key: '.mixin' }, { type: 'mixin' }),
|
|
1277
|
+
args: list([any('red')])
|
|
1278
|
+
})
|
|
1279
|
+
])
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
const testRuleset2 = ruleset({
|
|
1283
|
+
selector: el('.test2'),
|
|
1284
|
+
rules: rules([
|
|
1285
|
+
call({
|
|
1286
|
+
name: ref({ key: '.mixin' }, { type: 'mixin' }),
|
|
1287
|
+
args: list([any('blue')])
|
|
1288
|
+
})
|
|
1289
|
+
])
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
const root = rules([redMixin, blueMixin, testRuleset1, testRuleset2]);
|
|
1293
|
+
context.root = root;
|
|
1294
|
+
|
|
1295
|
+
const evald = await root.eval(context);
|
|
1296
|
+
const css = evald.render(context);
|
|
1297
|
+
|
|
1298
|
+
expect(css).toBeString(`
|
|
1299
|
+
.test1 {
|
|
1300
|
+
color: red;
|
|
1301
|
+
}
|
|
1302
|
+
.test2 {
|
|
1303
|
+
color: blue;
|
|
1304
|
+
}
|
|
1305
|
+
`);
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
it('matches a sequence pattern parameter against a state-patched argument value', async () => {
|
|
1309
|
+
const buildRoot = () => {
|
|
1310
|
+
const mixinDef = mixin({
|
|
1311
|
+
name: any('.mixin'),
|
|
1312
|
+
params: list([
|
|
1313
|
+
seq([num(10), num(20)])
|
|
1314
|
+
]),
|
|
1315
|
+
rules: rules([
|
|
1316
|
+
decl({ name: 'color', value: any('red') })
|
|
1317
|
+
])
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
const arg = seq([num(10), num(30)]);
|
|
1321
|
+
const testRuleset = ruleset({
|
|
1322
|
+
selector: el('.test'),
|
|
1323
|
+
rules: rules([
|
|
1324
|
+
call({
|
|
1325
|
+
name: ref({ key: '.mixin' }, { type: 'mixin' }),
|
|
1326
|
+
args: list([arg])
|
|
1327
|
+
})
|
|
1328
|
+
])
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1331
|
+
return {
|
|
1332
|
+
root: rules([mixinDef, testRuleset]),
|
|
1333
|
+
arg
|
|
1334
|
+
};
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1337
|
+
const baseline = buildRoot();
|
|
1338
|
+
await expectRejects(baseline.root.eval(new Context({ leakyRules: true })), ReferenceError, /No matching mixins/);
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
it('matches selector pattern params after preparing the selector operand before compare(context)', async () => {
|
|
1342
|
+
const buildRoot = (ctx: Context) => {
|
|
1343
|
+
const parent = ruleset({
|
|
1344
|
+
selector: el('.alpha'),
|
|
1345
|
+
rules: rules([])
|
|
1346
|
+
});
|
|
1347
|
+
parent.get('selector').keySetLibrary = ctx.selectorBits;
|
|
1348
|
+
|
|
1349
|
+
const patched = el('.beta');
|
|
1350
|
+
patched.keySetLibrary = ctx.selectorBits;
|
|
1351
|
+
|
|
1352
|
+
const find = sel([
|
|
1353
|
+
amp({ selectorContainer: parent as any }),
|
|
1354
|
+
co('>'),
|
|
1355
|
+
el('.tail')
|
|
1356
|
+
]);
|
|
1357
|
+
find.keySetLibrary = ctx.selectorBits;
|
|
1358
|
+
for (const child of find.get('value') as any[]) {
|
|
1359
|
+
if ('keySetLibrary' in child) {
|
|
1360
|
+
child.keySetLibrary = ctx.selectorBits;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
const patternArg = sellist([find]);
|
|
1364
|
+
patternArg.keySetLibrary = ctx.selectorBits;
|
|
1365
|
+
|
|
1366
|
+
const target = sel([el('.beta'), co('>'), el('.tail')]);
|
|
1367
|
+
const otherBits = new Context().selectorBits;
|
|
1368
|
+
target.keySetLibrary = otherBits;
|
|
1369
|
+
for (const child of target.get('value') as any[]) {
|
|
1370
|
+
if ('keySetLibrary' in child) {
|
|
1371
|
+
child.keySetLibrary = otherBits;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
const mixinDef = mixin({
|
|
1376
|
+
name: any('.mixin'),
|
|
1377
|
+
params: list([patternArg]),
|
|
1378
|
+
rules: rules([
|
|
1379
|
+
decl({ name: 'color', value: any('red') })
|
|
1380
|
+
])
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
const testRuleset = ruleset({
|
|
1384
|
+
selector: el('.test'),
|
|
1385
|
+
rules: rules([
|
|
1386
|
+
call({
|
|
1387
|
+
name: ref({ key: '.mixin' }, { type: 'mixin' }),
|
|
1388
|
+
args: list([target])
|
|
1389
|
+
})
|
|
1390
|
+
])
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
return {
|
|
1394
|
+
root: rules([mixinDef, testRuleset]),
|
|
1395
|
+
parent,
|
|
1396
|
+
patched,
|
|
1397
|
+
patternArg,
|
|
1398
|
+
target
|
|
1399
|
+
};
|
|
1400
|
+
};
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
it('should call a mixin with rest parameters', async () => {
|
|
1404
|
+
// Create a mixin with a rest parameter: .my-mixin(@a, @rest...) { margin: @rest; }
|
|
1405
|
+
const mixinDef = mixin({
|
|
1406
|
+
name: any('.my-mixin'),
|
|
1407
|
+
params: list([
|
|
1408
|
+
any('a', { role: 'property' }),
|
|
1409
|
+
rest('rest') // Rest parameter collects remaining arguments
|
|
1410
|
+
]),
|
|
1411
|
+
rules: rules([
|
|
1412
|
+
decl({ name: 'margin', value: ref({ key: 'rest' }, { type: 'variable' }) })
|
|
1413
|
+
])
|
|
1414
|
+
});
|
|
1415
|
+
|
|
1416
|
+
// Create a ruleset that calls the mixin with multiple args: .test { .my-mixin(10px, 20px, 30px); }
|
|
1417
|
+
const testRuleset = ruleset({
|
|
1418
|
+
selector: el('.test'),
|
|
1419
|
+
rules: rules([
|
|
1420
|
+
call({
|
|
1421
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
1422
|
+
args: list([any('10px'), any('20px'), any('30px')])
|
|
1423
|
+
})
|
|
1424
|
+
])
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
const root = rules([mixinDef, testRuleset]);
|
|
1428
|
+
context.root = root;
|
|
1429
|
+
|
|
1430
|
+
const evald = await root.eval(context);
|
|
1431
|
+
const css = evald.render(context);
|
|
1432
|
+
|
|
1433
|
+
expect(css).toBeString(`
|
|
1434
|
+
.test {
|
|
1435
|
+
margin: 20px 30px;
|
|
1436
|
+
}
|
|
1437
|
+
`);
|
|
1438
|
+
});
|
|
1439
|
+
|
|
1440
|
+
it('should call a mixin with unnamed rest parameter (auto-generated name)', async () => {
|
|
1441
|
+
// Create a mixin with an unnamed rest parameter: .my-mixin(@a, ...) { margin: @rest; }
|
|
1442
|
+
// The name should be auto-generated as "rest"
|
|
1443
|
+
const mixinDef = mixin({
|
|
1444
|
+
name: any('.my-mixin'),
|
|
1445
|
+
params: list([
|
|
1446
|
+
any('a', { role: 'property' }),
|
|
1447
|
+
rest(undefined) // Unnamed rest parameter - should auto-generate "rest"
|
|
1448
|
+
]),
|
|
1449
|
+
rules: rules([
|
|
1450
|
+
decl({ name: 'margin', value: ref({ key: 'rest' }, { type: 'variable' }) })
|
|
1451
|
+
])
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
// Create a ruleset that calls the mixin with multiple args: .test { .my-mixin(10px, 20px, 30px); }
|
|
1455
|
+
const testRuleset = ruleset({
|
|
1456
|
+
selector: el('.test'),
|
|
1457
|
+
rules: rules([
|
|
1458
|
+
call({
|
|
1459
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
1460
|
+
args: list([any('10px'), any('20px'), any('30px')])
|
|
1461
|
+
})
|
|
1462
|
+
])
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
const root = rules([mixinDef, testRuleset]);
|
|
1466
|
+
context.root = root;
|
|
1467
|
+
|
|
1468
|
+
const evald = await root.eval(context);
|
|
1469
|
+
const css = evald.render(context);
|
|
1470
|
+
|
|
1471
|
+
expect(css).toBeString(`
|
|
1472
|
+
.test {
|
|
1473
|
+
margin: 20px 30px;
|
|
1474
|
+
}
|
|
1475
|
+
`);
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
it('should call a mixin with multiple nested compound selectors', async () => {
|
|
1479
|
+
// .do .re .mi .fa {
|
|
1480
|
+
// .sol .la {
|
|
1481
|
+
// .si {
|
|
1482
|
+
// color: cyan;
|
|
1483
|
+
// }
|
|
1484
|
+
// }
|
|
1485
|
+
// }
|
|
1486
|
+
// .mutli-selector-parents {
|
|
1487
|
+
// .do.re.mi.fa.sol.la.si();
|
|
1488
|
+
// }
|
|
1489
|
+
const node = rules([
|
|
1490
|
+
ruleset({
|
|
1491
|
+
selector: sel([el('.do'), co(' '), el('.re'), co(' '), el('.mi'), co(' '), el('.fa')]) as any,
|
|
1492
|
+
rules: rules([
|
|
1493
|
+
ruleset({
|
|
1494
|
+
selector: sel([el('.sol'), co(' '), el('.la')]) as any,
|
|
1495
|
+
rules: rules([
|
|
1496
|
+
ruleset({
|
|
1497
|
+
selector: sel([el('.si')]) as any,
|
|
1498
|
+
rules: rules([
|
|
1499
|
+
decl({ name: 'color', value: any('cyan') })
|
|
1500
|
+
])
|
|
1501
|
+
})
|
|
1502
|
+
])
|
|
1503
|
+
})
|
|
1504
|
+
])
|
|
1505
|
+
}),
|
|
1506
|
+
ruleset({
|
|
1507
|
+
selector: el('.mutli-selector-parents'),
|
|
1508
|
+
rules: rules([
|
|
1509
|
+
call({ name: ref({ key: compound([el('.do'), el('.re'), el('.mi'), el('.fa'), el('.sol'), el('.la'), el('.si')]) }, { type: 'mixin-ruleset' }) })
|
|
1510
|
+
])
|
|
1511
|
+
})
|
|
1512
|
+
]);
|
|
1513
|
+
context.opts.collapseNesting = true;
|
|
1514
|
+
let evald = await node.eval(context);
|
|
1515
|
+
const css = evald.render(context);
|
|
1516
|
+
expect(css).toBeString(`
|
|
1517
|
+
.do .re .mi .fa .sol .la .si {
|
|
1518
|
+
color: cyan;
|
|
1519
|
+
}
|
|
1520
|
+
.mutli-selector-parents {
|
|
1521
|
+
color: cyan;
|
|
1522
|
+
}
|
|
1523
|
+
`);
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
it('should call a mixin or ruleset with different nesting patterns', async () => {
|
|
1527
|
+
Node.prototype.fullRender = false;
|
|
1528
|
+
// #theme() {
|
|
1529
|
+
// .dark() {
|
|
1530
|
+
// .navbar() {
|
|
1531
|
+
// @color: cyan;
|
|
1532
|
+
// }
|
|
1533
|
+
// }
|
|
1534
|
+
// }
|
|
1535
|
+
// #theme.dark.navbar {
|
|
1536
|
+
// @color: blue;
|
|
1537
|
+
// }
|
|
1538
|
+
// .rule {
|
|
1539
|
+
// #theme.dark.navbar();
|
|
1540
|
+
// background-color: @color;
|
|
1541
|
+
// }
|
|
1542
|
+
const node = rules([
|
|
1543
|
+
mixin({
|
|
1544
|
+
name: any('#theme'),
|
|
1545
|
+
rules: rules([
|
|
1546
|
+
mixin({
|
|
1547
|
+
name: any('.dark'),
|
|
1548
|
+
rules: rules([
|
|
1549
|
+
mixin({
|
|
1550
|
+
name: any('.navbar'),
|
|
1551
|
+
rules: rules([
|
|
1552
|
+
vardecl({ name: 'color', value: any('cyan') })
|
|
1553
|
+
])
|
|
1554
|
+
})
|
|
1555
|
+
])
|
|
1556
|
+
})
|
|
1557
|
+
])
|
|
1558
|
+
}),
|
|
1559
|
+
ruleset({
|
|
1560
|
+
selector: compound([el('#theme'), el('.dark'), el('.navbar')]),
|
|
1561
|
+
rules: rules([
|
|
1562
|
+
vardecl({ name: 'color', value: any('blue') })
|
|
1563
|
+
])
|
|
1564
|
+
}),
|
|
1565
|
+
ruleset({
|
|
1566
|
+
selector: el('.rule'),
|
|
1567
|
+
rules: rules([
|
|
1568
|
+
call({ name: ref({ key: ['#theme', '.dark', '.navbar'] }, { type: 'mixin-ruleset' }) }),
|
|
1569
|
+
decl({ name: 'background-color', value: ref({ key: 'color' }, { type: 'variable' }) })
|
|
1570
|
+
])
|
|
1571
|
+
})
|
|
1572
|
+
]);
|
|
1573
|
+
let evald = await node.eval(context);
|
|
1574
|
+
const css = evald.render(context);
|
|
1575
|
+
expect(css).toBeString(`
|
|
1576
|
+
.rule {
|
|
1577
|
+
background-color: cyan;
|
|
1578
|
+
}
|
|
1579
|
+
`);
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
it('should call a namespace mixin using ComplexSelector key (parser pattern)', async () => {
|
|
1583
|
+
// Mirrors the Less parser's AST for:
|
|
1584
|
+
// #theme {
|
|
1585
|
+
// > .mixin {
|
|
1586
|
+
// background-color: grey;
|
|
1587
|
+
// }
|
|
1588
|
+
// }
|
|
1589
|
+
// #container {
|
|
1590
|
+
// #theme > .mixin();
|
|
1591
|
+
// }
|
|
1592
|
+
const node = rules([
|
|
1593
|
+
ruleset({
|
|
1594
|
+
selector: el('#theme'),
|
|
1595
|
+
rules: rules([
|
|
1596
|
+
ruleset({
|
|
1597
|
+
selector: sel([co('>'), el('.mixin')]),
|
|
1598
|
+
rules: rules([
|
|
1599
|
+
decl({ name: 'background-color', value: any('grey') })
|
|
1600
|
+
])
|
|
1601
|
+
})
|
|
1602
|
+
])
|
|
1603
|
+
}),
|
|
1604
|
+
ruleset({
|
|
1605
|
+
selector: el('#container'),
|
|
1606
|
+
rules: rules([
|
|
1607
|
+
call({
|
|
1608
|
+
name: ref(
|
|
1609
|
+
{ key: sel([el('#theme'), co('>'), el('.mixin')]) },
|
|
1610
|
+
{ type: 'mixin-ruleset' }
|
|
1611
|
+
)
|
|
1612
|
+
})
|
|
1613
|
+
])
|
|
1614
|
+
})
|
|
1615
|
+
]);
|
|
1616
|
+
let evald = await node.eval(context);
|
|
1617
|
+
const css = evald.render(context);
|
|
1618
|
+
expect(css).toContain('background-color: grey');
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
it('should call a namespace mixin using ComplexSelector key with collapseNesting', async () => {
|
|
1622
|
+
// Same as above but with collapseNesting: true (matching Less compiler behavior)
|
|
1623
|
+
const collapseContext = new Context({
|
|
1624
|
+
leakyRules: true,
|
|
1625
|
+
collapseNesting: true
|
|
1626
|
+
});
|
|
1627
|
+
const node = rules([
|
|
1628
|
+
ruleset({
|
|
1629
|
+
selector: el('#theme'),
|
|
1630
|
+
rules: rules([
|
|
1631
|
+
ruleset({
|
|
1632
|
+
selector: sel([co('>'), el('.mixin')]),
|
|
1633
|
+
rules: rules([
|
|
1634
|
+
decl({ name: 'background-color', value: any('grey') })
|
|
1635
|
+
])
|
|
1636
|
+
})
|
|
1637
|
+
])
|
|
1638
|
+
}),
|
|
1639
|
+
ruleset({
|
|
1640
|
+
selector: el('#container'),
|
|
1641
|
+
rules: rules([
|
|
1642
|
+
call({
|
|
1643
|
+
name: ref(
|
|
1644
|
+
{ key: sel([el('#theme'), co('>'), el('.mixin')]) },
|
|
1645
|
+
{ type: 'mixin-ruleset' }
|
|
1646
|
+
)
|
|
1647
|
+
})
|
|
1648
|
+
])
|
|
1649
|
+
})
|
|
1650
|
+
]);
|
|
1651
|
+
let evald = await node.eval(collapseContext);
|
|
1652
|
+
const css = evald.render(context);
|
|
1653
|
+
expect(css).toContain('background-color: grey');
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
it('top-level ruleset-as-mixin output keeps nested descendants unbound from definition selector', async () => {
|
|
1657
|
+
const node = rules([
|
|
1658
|
+
ruleset({
|
|
1659
|
+
selector: el('.zz'),
|
|
1660
|
+
rules: rules([
|
|
1661
|
+
ruleset({
|
|
1662
|
+
selector: el('.y'),
|
|
1663
|
+
rules: rules([
|
|
1664
|
+
decl({ name: 'pulled-in', value: any('yes') })
|
|
1665
|
+
])
|
|
1666
|
+
})
|
|
1667
|
+
])
|
|
1668
|
+
}),
|
|
1669
|
+
call({
|
|
1670
|
+
name: ref({ key: '.zz' }, { type: 'mixin-ruleset' })
|
|
1671
|
+
})
|
|
1672
|
+
]);
|
|
1673
|
+
|
|
1674
|
+
context.opts.collapseNesting = true;
|
|
1675
|
+
|
|
1676
|
+
const evald = await node.eval(context);
|
|
1677
|
+
const css = evald.render(context);
|
|
1678
|
+
|
|
1679
|
+
expect(css).toBeString(`
|
|
1680
|
+
.zz .y {
|
|
1681
|
+
pulled-in: yes;
|
|
1682
|
+
}
|
|
1683
|
+
.y {
|
|
1684
|
+
pulled-in: yes;
|
|
1685
|
+
}
|
|
1686
|
+
`);
|
|
1687
|
+
});
|
|
1688
|
+
});
|
|
1689
|
+
|
|
1690
|
+
describe('rest parameter matching and assignment', () => {
|
|
1691
|
+
it('should match a mixin with rest parameter and assign empty rest when no extra args provided', async () => {
|
|
1692
|
+
// Create a mixin with a rest parameter: .my-mixin(@a, @rest...) { padding: @rest; }
|
|
1693
|
+
const mixinDef = mixin({
|
|
1694
|
+
name: any('.my-mixin'),
|
|
1695
|
+
params: list([
|
|
1696
|
+
any('a', { role: 'property' }),
|
|
1697
|
+
rest('rest')
|
|
1698
|
+
]),
|
|
1699
|
+
rules: rules([
|
|
1700
|
+
decl({ name: 'padding', value: ref({ key: 'rest' }, { type: 'variable' }) })
|
|
1701
|
+
])
|
|
1702
|
+
});
|
|
1703
|
+
|
|
1704
|
+
// Create a ruleset that calls the mixin with only the required arg: .test { .my-mixin(10px); }
|
|
1705
|
+
const testRuleset = ruleset({
|
|
1706
|
+
selector: el('.test'),
|
|
1707
|
+
rules: rules([
|
|
1708
|
+
call({
|
|
1709
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
1710
|
+
args: list([any('10px')]) // Only one arg, rest should be empty
|
|
1711
|
+
})
|
|
1712
|
+
])
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
const root = rules([mixinDef, testRuleset]);
|
|
1716
|
+
context.root = root;
|
|
1717
|
+
|
|
1718
|
+
const evald = await root.eval(context);
|
|
1719
|
+
const css = evald.render(context);
|
|
1720
|
+
|
|
1721
|
+
expect(css).toBeString(`
|
|
1722
|
+
.test {
|
|
1723
|
+
padding: rest;
|
|
1724
|
+
}
|
|
1725
|
+
`);
|
|
1726
|
+
});
|
|
1727
|
+
|
|
1728
|
+
it('should match a mixin with rest parameter and assign single value to rest', async () => {
|
|
1729
|
+
// Create a mixin with a rest parameter: .my-mixin(@a, @rest...) { margin: @rest; }
|
|
1730
|
+
const mixinDef = mixin({
|
|
1731
|
+
name: any('.my-mixin'),
|
|
1732
|
+
params: list([
|
|
1733
|
+
any('a', { role: 'property' }),
|
|
1734
|
+
rest('rest')
|
|
1735
|
+
]),
|
|
1736
|
+
rules: rules([
|
|
1737
|
+
decl({ name: 'margin', value: ref({ key: 'rest' }, { type: 'variable' }) })
|
|
1738
|
+
])
|
|
1739
|
+
});
|
|
1740
|
+
|
|
1741
|
+
// Create a ruleset that calls the mixin with two args: .test { .my-mixin(10px, 20px); }
|
|
1742
|
+
const testRuleset = ruleset({
|
|
1743
|
+
selector: el('.test'),
|
|
1744
|
+
rules: rules([
|
|
1745
|
+
call({
|
|
1746
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
1747
|
+
args: list([any('10px'), any('20px')]) // Rest should contain 20px
|
|
1748
|
+
})
|
|
1749
|
+
])
|
|
1750
|
+
});
|
|
1751
|
+
|
|
1752
|
+
const root = rules([mixinDef, testRuleset]);
|
|
1753
|
+
context.root = root;
|
|
1754
|
+
|
|
1755
|
+
const evald = await root.eval(context);
|
|
1756
|
+
const css = evald.render(context);
|
|
1757
|
+
|
|
1758
|
+
expect(css).toBeString(`
|
|
1759
|
+
.test {
|
|
1760
|
+
margin: 20px;
|
|
1761
|
+
}
|
|
1762
|
+
`);
|
|
1763
|
+
});
|
|
1764
|
+
|
|
1765
|
+
it('should match a mixin with rest parameter and assign multiple values to rest', async () => {
|
|
1766
|
+
// Create a mixin with a rest parameter: .my-mixin(@a, @rest...) { padding: @rest; }
|
|
1767
|
+
const mixinDef = mixin({
|
|
1768
|
+
name: any('.my-mixin'),
|
|
1769
|
+
params: list([
|
|
1770
|
+
any('a', { role: 'property' }),
|
|
1771
|
+
rest('rest')
|
|
1772
|
+
]),
|
|
1773
|
+
rules: rules([
|
|
1774
|
+
decl({ name: 'padding', value: ref({ key: 'rest' }, { type: 'variable' }) })
|
|
1775
|
+
])
|
|
1776
|
+
});
|
|
1777
|
+
|
|
1778
|
+
// Create a ruleset that calls the mixin with many args: .test { .my-mixin(10px, 20px, 30px, 40px); }
|
|
1779
|
+
const testRuleset = ruleset({
|
|
1780
|
+
selector: el('.test'),
|
|
1781
|
+
rules: rules([
|
|
1782
|
+
call({
|
|
1783
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
1784
|
+
args: list([any('10px'), any('20px'), any('30px'), any('40px')]) // Rest should contain 20px, 30px, 40px
|
|
1785
|
+
})
|
|
1786
|
+
])
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
const root = rules([mixinDef, testRuleset]);
|
|
1790
|
+
context.root = root;
|
|
1791
|
+
|
|
1792
|
+
const evald = await root.eval(context);
|
|
1793
|
+
const css = evald.render(context);
|
|
1794
|
+
|
|
1795
|
+
expect(css).toBeString(`
|
|
1796
|
+
.test {
|
|
1797
|
+
padding: 20px 30px 40px;
|
|
1798
|
+
}
|
|
1799
|
+
`);
|
|
1800
|
+
});
|
|
1801
|
+
|
|
1802
|
+
it('should match a mixin with rest parameter when multiple required params before rest', async () => {
|
|
1803
|
+
// Create a mixin with multiple params before rest: .my-mixin(@a, @b, @rest...) { margin: @rest; }
|
|
1804
|
+
const mixinDef = mixin({
|
|
1805
|
+
name: any('.my-mixin'),
|
|
1806
|
+
params: list([
|
|
1807
|
+
any('a', { role: 'property' }),
|
|
1808
|
+
any('b', { role: 'property' }),
|
|
1809
|
+
rest('rest')
|
|
1810
|
+
]),
|
|
1811
|
+
rules: rules([
|
|
1812
|
+
decl({ name: 'margin', value: ref({ key: 'rest' }, { type: 'variable' }) })
|
|
1813
|
+
])
|
|
1814
|
+
});
|
|
1815
|
+
|
|
1816
|
+
// Create a ruleset that calls the mixin: .test { .my-mixin(10px, 20px, 30px, 40px); }
|
|
1817
|
+
const testRuleset = ruleset({
|
|
1818
|
+
selector: el('.test'),
|
|
1819
|
+
rules: rules([
|
|
1820
|
+
call({
|
|
1821
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
1822
|
+
args: list([any('10px'), any('20px'), any('30px'), any('40px')]) // Rest should contain 30px, 40px
|
|
1823
|
+
})
|
|
1824
|
+
])
|
|
1825
|
+
});
|
|
1826
|
+
|
|
1827
|
+
const root = rules([mixinDef, testRuleset]);
|
|
1828
|
+
context.root = root;
|
|
1829
|
+
|
|
1830
|
+
const evald = await root.eval(context);
|
|
1831
|
+
const css = evald.render(context);
|
|
1832
|
+
|
|
1833
|
+
expect(css).toBeString(`
|
|
1834
|
+
.test {
|
|
1835
|
+
margin: 30px 40px;
|
|
1836
|
+
}
|
|
1837
|
+
`);
|
|
1838
|
+
});
|
|
1839
|
+
|
|
1840
|
+
it('should use rest variable in multiple declarations within mixin', async () => {
|
|
1841
|
+
// Create a mixin that uses rest in multiple places: .my-mixin(@a, @rest...) { margin: @rest; padding: @rest; }
|
|
1842
|
+
const mixinDef = mixin({
|
|
1843
|
+
name: any('.my-mixin'),
|
|
1844
|
+
params: list([
|
|
1845
|
+
any('a', { role: 'property' }),
|
|
1846
|
+
rest('rest')
|
|
1847
|
+
]),
|
|
1848
|
+
rules: rules([
|
|
1849
|
+
decl({ name: 'margin', value: ref({ key: 'rest' }, { type: 'variable' }) }),
|
|
1850
|
+
decl({ name: 'padding', value: ref({ key: 'rest' }, { type: 'variable' }) })
|
|
1851
|
+
])
|
|
1852
|
+
});
|
|
1853
|
+
|
|
1854
|
+
// Create a ruleset that calls the mixin: .test { .my-mixin(10px, 20px, 30px); }
|
|
1855
|
+
const testRuleset = ruleset({
|
|
1856
|
+
selector: el('.test'),
|
|
1857
|
+
rules: rules([
|
|
1858
|
+
call({
|
|
1859
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
1860
|
+
args: list([any('10px'), any('20px'), any('30px')])
|
|
1861
|
+
})
|
|
1862
|
+
])
|
|
1863
|
+
});
|
|
1864
|
+
|
|
1865
|
+
const root = rules([mixinDef, testRuleset]);
|
|
1866
|
+
context.root = root;
|
|
1867
|
+
|
|
1868
|
+
const evald = await root.eval(context);
|
|
1869
|
+
const css = evald.render(context);
|
|
1870
|
+
|
|
1871
|
+
expect(css).toBeString(`
|
|
1872
|
+
.test {
|
|
1873
|
+
margin: 20px 30px;
|
|
1874
|
+
padding: 20px 30px;
|
|
1875
|
+
}
|
|
1876
|
+
`);
|
|
1877
|
+
});
|
|
1878
|
+
|
|
1879
|
+
it('should match mixin with rest parameter over mixin without rest when both exist', async () => {
|
|
1880
|
+
// Create a mixin without rest: .my-mixin(@a, @b) { color: red; }
|
|
1881
|
+
const mixinWithoutRest = mixin({
|
|
1882
|
+
name: any('.my-mixin'),
|
|
1883
|
+
params: list([
|
|
1884
|
+
any('a', { role: 'property' }),
|
|
1885
|
+
any('b', { role: 'property' })
|
|
1886
|
+
]),
|
|
1887
|
+
rules: rules([
|
|
1888
|
+
decl({ name: 'color', value: any('red') })
|
|
1889
|
+
])
|
|
1890
|
+
});
|
|
1891
|
+
|
|
1892
|
+
// Create a mixin with rest: .my-mixin(@a, @rest...) { color: blue; }
|
|
1893
|
+
const mixinWithRest = mixin({
|
|
1894
|
+
name: any('.my-mixin'),
|
|
1895
|
+
params: list([
|
|
1896
|
+
any('a', { role: 'property' }),
|
|
1897
|
+
rest('rest')
|
|
1898
|
+
]),
|
|
1899
|
+
rules: rules([
|
|
1900
|
+
decl({ name: 'color', value: any('blue') })
|
|
1901
|
+
])
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
// Create a ruleset that calls with exact 2 args: .test1 { .my-mixin(10px, 20px); }
|
|
1905
|
+
const testRuleset1 = ruleset({
|
|
1906
|
+
selector: el('.test1'),
|
|
1907
|
+
rules: rules([
|
|
1908
|
+
call({
|
|
1909
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
1910
|
+
args: list([any('10px'), any('20px')]) // Matches mixinWithoutRest exactly
|
|
1911
|
+
})
|
|
1912
|
+
])
|
|
1913
|
+
});
|
|
1914
|
+
|
|
1915
|
+
// Create a ruleset that calls with 3 args: .test2 { .my-mixin(10px, 20px, 30px); }
|
|
1916
|
+
const testRuleset2 = ruleset({
|
|
1917
|
+
selector: el('.test2'),
|
|
1918
|
+
rules: rules([
|
|
1919
|
+
call({
|
|
1920
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
1921
|
+
args: list([any('10px'), any('20px'), any('30px')]) // Should match mixinWithRest
|
|
1922
|
+
})
|
|
1923
|
+
])
|
|
1924
|
+
});
|
|
1925
|
+
|
|
1926
|
+
const root = rules([mixinWithoutRest, mixinWithRest, testRuleset1, testRuleset2]);
|
|
1927
|
+
context.root = root;
|
|
1928
|
+
|
|
1929
|
+
const evald = await root.eval(context);
|
|
1930
|
+
const css = evald.render(context);
|
|
1931
|
+
|
|
1932
|
+
expect(css).toBeString(`
|
|
1933
|
+
.test1 {
|
|
1934
|
+
color: red;
|
|
1935
|
+
color: blue;
|
|
1936
|
+
}
|
|
1937
|
+
.test2 {
|
|
1938
|
+
color: blue;
|
|
1939
|
+
}
|
|
1940
|
+
`);
|
|
1941
|
+
});
|
|
1942
|
+
});
|
|
1943
|
+
|
|
1944
|
+
describe('arity failures', () => {
|
|
1945
|
+
it('should fail when calling a mixin with too few arguments (missing required parameter)', async () => {
|
|
1946
|
+
// Create a mixin with a required parameter: .my-mixin(@color) { color: @color; }
|
|
1947
|
+
const mixinDef = mixin({
|
|
1948
|
+
name: any('.my-mixin'),
|
|
1949
|
+
params: list([
|
|
1950
|
+
any('color', { role: 'property' }) // Required parameter without default
|
|
1951
|
+
]),
|
|
1952
|
+
rules: rules([
|
|
1953
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
|
|
1954
|
+
])
|
|
1955
|
+
});
|
|
1956
|
+
|
|
1957
|
+
// Create a ruleset that calls the mixin without args: .test { .my-mixin(); }
|
|
1958
|
+
const testRuleset = ruleset({
|
|
1959
|
+
selector: el('.test'),
|
|
1960
|
+
rules: rules([
|
|
1961
|
+
call({ name: ref({ key: '.my-mixin' }, { type: 'mixin' }) })
|
|
1962
|
+
])
|
|
1963
|
+
});
|
|
1964
|
+
|
|
1965
|
+
const root = rules([mixinDef, testRuleset]);
|
|
1966
|
+
context.root = root;
|
|
1967
|
+
|
|
1968
|
+
await expectRejects(root.eval(context), ReferenceError, /No matching mixins/);
|
|
1969
|
+
});
|
|
1970
|
+
|
|
1971
|
+
it('should fail when calling a mixin with too few arguments (multiple required parameters)', async () => {
|
|
1972
|
+
// Create a mixin with multiple required parameters: .my-mixin(@color, @size) { color: @color; font-size: @size; }
|
|
1973
|
+
const mixinDef = mixin({
|
|
1974
|
+
name: any('.my-mixin'),
|
|
1975
|
+
params: list([
|
|
1976
|
+
any('color', { role: 'property' }),
|
|
1977
|
+
any('size', { role: 'property' })
|
|
1978
|
+
]),
|
|
1979
|
+
rules: rules([
|
|
1980
|
+
decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) }),
|
|
1981
|
+
decl({ name: 'font-size', value: ref({ key: 'size' }, { type: 'variable' }) })
|
|
1982
|
+
])
|
|
1983
|
+
});
|
|
1984
|
+
|
|
1985
|
+
// Create a ruleset that calls the mixin with only one arg: .test { .my-mixin(red); }
|
|
1986
|
+
const testRuleset = ruleset({
|
|
1987
|
+
selector: el('.test'),
|
|
1988
|
+
rules: rules([
|
|
1989
|
+
call({
|
|
1990
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
1991
|
+
args: list([any('red')]) // Only one argument, but two are required
|
|
1992
|
+
})
|
|
1993
|
+
])
|
|
1994
|
+
});
|
|
1995
|
+
|
|
1996
|
+
const root = rules([mixinDef, testRuleset]);
|
|
1997
|
+
context.root = root;
|
|
1998
|
+
|
|
1999
|
+
await expectRejects(root.eval(context), ReferenceError, /No matching mixins/);
|
|
2000
|
+
});
|
|
2001
|
+
|
|
2002
|
+
it('should fail when calling a mixin with no parameters but providing arguments', async () => {
|
|
2003
|
+
// Create a mixin with no parameters: .my-mixin() { color: red; }
|
|
2004
|
+
const mixinDef = mixin({
|
|
2005
|
+
name: any('.my-mixin'),
|
|
2006
|
+
rules: rules([
|
|
2007
|
+
decl({ name: 'color', value: any('red') })
|
|
2008
|
+
])
|
|
2009
|
+
});
|
|
2010
|
+
|
|
2011
|
+
// Create a ruleset that calls the mixin with args: .test { .my-mixin(blue); }
|
|
2012
|
+
const testRuleset = ruleset({
|
|
2013
|
+
selector: el('.test'),
|
|
2014
|
+
rules: rules([
|
|
2015
|
+
call({
|
|
2016
|
+
name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
|
|
2017
|
+
args: list([any('blue')]) // One argument, but mixin has no parameters
|
|
2018
|
+
})
|
|
2019
|
+
])
|
|
2020
|
+
});
|
|
2021
|
+
|
|
2022
|
+
const root = rules([mixinDef, testRuleset]);
|
|
2023
|
+
context.root = root;
|
|
2024
|
+
|
|
2025
|
+
await expectRejects(root.eval(context), ReferenceError, /No matching mixins/);
|
|
2026
|
+
});
|
|
2027
|
+
|
|
2028
|
+
});
|
|
2029
|
+
|
|
2030
|
+
describe('serialization', () => {
|
|
2031
|
+
it('should serialize a mixin', () => {
|
|
2032
|
+
const rule = mixin({
|
|
2033
|
+
name: any('myMixin'),
|
|
2034
|
+
rules: rules([
|
|
2035
|
+
decl({ name: 'color', value: any('black') }),
|
|
2036
|
+
decl({ name: 'background-color', value: any('white') })
|
|
2037
|
+
])
|
|
2038
|
+
});
|
|
2039
|
+
expect(`${rule}`).toBeString(`
|
|
2040
|
+
myMixin() {
|
|
2041
|
+
color: black;
|
|
2042
|
+
background-color: white;
|
|
2043
|
+
}
|
|
2044
|
+
`);
|
|
2045
|
+
});
|
|
2046
|
+
|
|
2047
|
+
it('should serialize a mixin with args', () => {
|
|
2048
|
+
const rule = mixin({
|
|
2049
|
+
name: any('my-mixin'),
|
|
2050
|
+
params: list([
|
|
2051
|
+
vardecl({ name: 'a', value: any('black') }, { paramVar: true }),
|
|
2052
|
+
vardecl({ name: 'b', value: any('white') }, { paramVar: true })
|
|
2053
|
+
], { sep: ';' }),
|
|
2054
|
+
rules: rules([
|
|
2055
|
+
decl({ name: 'color', value: any('black') }),
|
|
2056
|
+
decl({ name: 'background-color', value: any('white') })
|
|
2057
|
+
])
|
|
2058
|
+
});
|
|
2059
|
+
expect(`${rule}`).toBeString(`
|
|
2060
|
+
my-mixin($a: black; $b: white) {
|
|
2061
|
+
color: black;
|
|
2062
|
+
background-color: white;
|
|
2063
|
+
}
|
|
2064
|
+
`);
|
|
2065
|
+
});
|
|
2066
|
+
|
|
2067
|
+
it('should serialize a guard', () => {
|
|
2068
|
+
const rule = mixin({
|
|
2069
|
+
name: any('my-mixin'),
|
|
2070
|
+
params: list([
|
|
2071
|
+
vardecl({ name: 'a', value: any('black') }, { paramVar: true }),
|
|
2072
|
+
vardecl({ name: 'b', value: any('white') }, { paramVar: true })
|
|
2073
|
+
], { sep: ';' }),
|
|
2074
|
+
guard: condition([expr(ref({ key: 'a' })), '=', expr(ref({ key: 'b' }))]),
|
|
2075
|
+
rules: rules([
|
|
2076
|
+
decl({ name: 'color', value: any('black') }),
|
|
2077
|
+
decl({ name: 'background-color', value: any('white') })
|
|
2078
|
+
])
|
|
2079
|
+
});
|
|
2080
|
+
expect(`${rule}`).toBeString(`
|
|
2081
|
+
my-mixin($a: black; $b: white) when ($($a) = $($b)) {
|
|
2082
|
+
color: black;
|
|
2083
|
+
background-color: white;
|
|
2084
|
+
}
|
|
2085
|
+
`);
|
|
2086
|
+
});
|
|
2087
|
+
});
|
|
2088
|
+
|
|
2089
|
+
describe('parent selector composition in mixin calls', () => {
|
|
2090
|
+
it('should resolve > li to caller context, not mixin ancestor chain, inside @media', async () => {
|
|
2091
|
+
/**
|
|
2092
|
+
* Less input:
|
|
2093
|
+
* .nav-justified {
|
|
2094
|
+
* @media (min-width: 480px) {
|
|
2095
|
+
* > li { display: table-cell; }
|
|
2096
|
+
* }
|
|
2097
|
+
* }
|
|
2098
|
+
* .menu {
|
|
2099
|
+
* @media (min-width: 768px) {
|
|
2100
|
+
* .nav-justified();
|
|
2101
|
+
* }
|
|
2102
|
+
* }
|
|
2103
|
+
*
|
|
2104
|
+
* Expected CSS (collapsed):
|
|
2105
|
+
* @media (min-width: 480px) { .nav-justified > li { ... } }
|
|
2106
|
+
* @media (min-width: 768px) { @media (min-width: 480px) { .menu > li { ... } } }
|
|
2107
|
+
*
|
|
2108
|
+
* Bug: produces `.menu .nav-justified > li` — the mixin body's
|
|
2109
|
+
* `> li` ruleset still walks up the parent chain to `.nav-justified`
|
|
2110
|
+
* instead of using the caller's captured frames.
|
|
2111
|
+
*/
|
|
2112
|
+
const navJustified = ruleset({
|
|
2113
|
+
selector: el('.nav-justified'),
|
|
2114
|
+
rules: rules([
|
|
2115
|
+
atrule({
|
|
2116
|
+
name: any('@media'),
|
|
2117
|
+
prelude: any('(min-width: 480px)'),
|
|
2118
|
+
rules: rules([
|
|
2119
|
+
ruleset({
|
|
2120
|
+
selector: sel([co('>'), el('li')]),
|
|
2121
|
+
rules: rules([
|
|
2122
|
+
decl({ name: 'display', value: any('table-cell') })
|
|
2123
|
+
])
|
|
2124
|
+
})
|
|
2125
|
+
])
|
|
2126
|
+
})
|
|
2127
|
+
])
|
|
2128
|
+
});
|
|
2129
|
+
|
|
2130
|
+
const menu = ruleset({
|
|
2131
|
+
selector: el('.menu'),
|
|
2132
|
+
rules: rules([
|
|
2133
|
+
atrule({
|
|
2134
|
+
name: any('@media'),
|
|
2135
|
+
prelude: any('(min-width: 768px)'),
|
|
2136
|
+
rules: rules([
|
|
2137
|
+
call({ name: ref({ key: '.nav-justified' }, { type: 'mixin-ruleset' }) })
|
|
2138
|
+
])
|
|
2139
|
+
})
|
|
2140
|
+
])
|
|
2141
|
+
});
|
|
2142
|
+
|
|
2143
|
+
const root = rules([navJustified, menu]);
|
|
2144
|
+
context.root = root;
|
|
2145
|
+
|
|
2146
|
+
const evald = await root.eval(context);
|
|
2147
|
+
const css = evald.toString({ collapseNesting: true, context });
|
|
2148
|
+
|
|
2149
|
+
expect(css).toContain('.menu > li');
|
|
2150
|
+
expect(css).not.toContain('.menu .nav-justified');
|
|
2151
|
+
});
|
|
2152
|
+
});
|
|
2153
|
+
|
|
2154
|
+
// it('should serialize to a module', () => {
|
|
2155
|
+
// let rule = mixin({
|
|
2156
|
+
// name: ident('myMixin'),
|
|
2157
|
+
// value: ruleset([
|
|
2158
|
+
// decl({ name: 'color', value: any('black') }),
|
|
2159
|
+
// decl({ name: 'background-color', value: any('white') })
|
|
2160
|
+
// ])
|
|
2161
|
+
// })
|
|
2162
|
+
// rule.toModule(context, out)
|
|
2163
|
+
// expect(out.toString()).toBe(
|
|
2164
|
+
// 'let myMixin = function() { return $J.ruleset(\n (() => {\n const $OUT = []\n $OUT.push($J.decl({\n name: $J.any("color"),\n value: $J.any("black")\n }))\n $OUT.push($J.decl({\n name: $J.any("background-color"),\n value: $J.any("white")\n }))\n return $OUT\n })()\n)}'
|
|
2165
|
+
// )
|
|
2166
|
+
// expect(rule.value.obj()).toEqual({
|
|
2167
|
+
// color: 'black',
|
|
2168
|
+
// 'background-color': 'white'
|
|
2169
|
+
// })
|
|
2170
|
+
// })
|
|
2171
|
+
});
|