@jesscss/core 2.0.0-alpha.4 → 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 -94
- package/lib/tree/ampersand.d.ts.map +0 -1
- package/lib/tree/ampersand.js +0 -269
- 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,1967 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, beforeAll, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
style,
|
|
4
|
+
rules,
|
|
5
|
+
sel,
|
|
6
|
+
el,
|
|
7
|
+
compound,
|
|
8
|
+
sellist,
|
|
9
|
+
decl,
|
|
10
|
+
vardecl,
|
|
11
|
+
any,
|
|
12
|
+
ref,
|
|
13
|
+
ruleset,
|
|
14
|
+
mixin,
|
|
15
|
+
call,
|
|
16
|
+
list,
|
|
17
|
+
amp,
|
|
18
|
+
co,
|
|
19
|
+
pseudo,
|
|
20
|
+
quoted,
|
|
21
|
+
url,
|
|
22
|
+
Interpolated,
|
|
23
|
+
INTERPOLATION_PLACEHOLDER,
|
|
24
|
+
type Rules,
|
|
25
|
+
Node,
|
|
26
|
+
Ruleset
|
|
27
|
+
} from '../index.js';
|
|
28
|
+
import { isNode } from '../util/is-node.js';
|
|
29
|
+
import { N } from '../node-type.js';
|
|
30
|
+
import { Context } from '../../context.js';
|
|
31
|
+
import type { FindOptions } from '../util/registry-utils.js';
|
|
32
|
+
import { resolve } from 'node:path';
|
|
33
|
+
import { createTestContext } from './import-style-test-helpers.js';
|
|
34
|
+
import { getParent } from '../util/field-helpers.js';
|
|
35
|
+
import { addEdge } from '../util/cursor.js';
|
|
36
|
+
|
|
37
|
+
let context: Context;
|
|
38
|
+
|
|
39
|
+
function getVarWithContext(context: Context, n: Rules, key: string, opts: FindOptions = {}) {
|
|
40
|
+
context.rulesContext = n;
|
|
41
|
+
opts.context ??= context;
|
|
42
|
+
opts.searchParents = true;
|
|
43
|
+
return n.find('declaration', key, 'VarDeclaration', opts);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getMixinWithContext(context: Context, n: Rules, key: string, opts: FindOptions = {}) {
|
|
47
|
+
context.rulesContext = n;
|
|
48
|
+
opts.context ??= context;
|
|
49
|
+
opts.searchParents = true;
|
|
50
|
+
return n.find('mixin', key, 'Mixin', opts);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getRulesetWithContext(context: Context, n: Rules, keys: string | string[], opts: FindOptions = {}) {
|
|
54
|
+
context.rulesContext = n;
|
|
55
|
+
opts.context ??= context;
|
|
56
|
+
opts.searchParents = true;
|
|
57
|
+
const keySet = typeof keys === 'string' ? [keys] : keys;
|
|
58
|
+
return n.find('ruleset', keySet, undefined, opts);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function asRulesContext(context: Context, rules: Rules): Context {
|
|
62
|
+
return {
|
|
63
|
+
...context,
|
|
64
|
+
renderKey: rules.renderKey,
|
|
65
|
+
rulesContext: rules
|
|
66
|
+
} as Context;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe('Style import', () => {
|
|
70
|
+
beforeAll(() => {
|
|
71
|
+
Node.prototype.fullRender = true;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
context = createTestContext();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('variable visibility', () => {
|
|
79
|
+
it('import type can see parent rules variables', async () => {
|
|
80
|
+
// Set up imported file - use absolute path
|
|
81
|
+
const importedPath = resolve(process.cwd(), 'imported.jess');
|
|
82
|
+
context.sourceTrees.set(importedPath, rules([
|
|
83
|
+
ruleset({
|
|
84
|
+
selector: sellist([sel([el('.imported')])]),
|
|
85
|
+
rules: rules([
|
|
86
|
+
decl({ name: any('color'), value: ref('parentVar', { type: 'variable' }) })
|
|
87
|
+
])
|
|
88
|
+
})
|
|
89
|
+
]));
|
|
90
|
+
|
|
91
|
+
// Parent file with variable
|
|
92
|
+
const parentVar = vardecl({ name: 'parentVar', value: any('red') });
|
|
93
|
+
const node = rules([
|
|
94
|
+
parentVar,
|
|
95
|
+
style({
|
|
96
|
+
path: quoted(any('imported.jess'))
|
|
97
|
+
}, {
|
|
98
|
+
type: 'import'
|
|
99
|
+
})
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
const evald = await node.eval(context);
|
|
103
|
+
const importedRules = evald.at(1, context) as Rules;
|
|
104
|
+
const importedRuleset = importedRules.at(0, context);
|
|
105
|
+
const importedCtx = asRulesContext(context, (importedRuleset as any).rules);
|
|
106
|
+
|
|
107
|
+
// The imported ruleset should be able to reference the parent variable
|
|
108
|
+
// The declaration should already be evaluated as part of the ruleset evaluation
|
|
109
|
+
const importedDecl = (importedRuleset as any).rules.at(0, importedCtx);
|
|
110
|
+
expect(importedDecl.toTrimmedString({ context: importedCtx })).toBe('color: red');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('import placements can reuse canonical imported rulesets while resolving different parent vars', async () => {
|
|
114
|
+
const importedPath = resolve(process.cwd(), 'imported-shared-ambient.jess');
|
|
115
|
+
const sourceRuleset = ruleset({
|
|
116
|
+
selector: sellist([sel([el('.imported')])]),
|
|
117
|
+
rules: rules([
|
|
118
|
+
decl({ name: any('color'), value: ref('parentVar', { type: 'variable' }) })
|
|
119
|
+
])
|
|
120
|
+
});
|
|
121
|
+
const sourceBody = sourceRuleset.get('rules');
|
|
122
|
+
const sourceDecl = sourceBody.at(0, context);
|
|
123
|
+
const sourceTree = rules([sourceRuleset]);
|
|
124
|
+
|
|
125
|
+
const evaluateImport = async (color: string) => {
|
|
126
|
+
const localContext = createTestContext();
|
|
127
|
+
localContext.sourceTrees.set(importedPath, sourceTree);
|
|
128
|
+
const entry = rules([
|
|
129
|
+
vardecl({ name: 'parentVar', value: any(color) }),
|
|
130
|
+
style({ path: quoted(any('imported-shared-ambient.jess')) }, { type: 'import' })
|
|
131
|
+
]);
|
|
132
|
+
const evald = await entry.eval(localContext);
|
|
133
|
+
return {
|
|
134
|
+
localContext,
|
|
135
|
+
evald,
|
|
136
|
+
importedRules: evald.at(1, localContext) as Rules,
|
|
137
|
+
importedRuleset: (evald.at(1, localContext) as Rules).at(0, localContext) as Ruleset
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const red = await evaluateImport('red');
|
|
142
|
+
const blue = await evaluateImport('blue');
|
|
143
|
+
|
|
144
|
+
const redDecl = (red.importedRuleset as any).rules.at(0, red.localContext);
|
|
145
|
+
const blueDecl = (blue.importedRuleset as any).rules.at(0, blue.localContext);
|
|
146
|
+
|
|
147
|
+
expect(red.importedRuleset).not.toBe(blue.importedRuleset);
|
|
148
|
+
expect(red.importedRuleset.sourceNode).toBe(sourceRuleset);
|
|
149
|
+
expect(blue.importedRuleset.sourceNode).toBe(sourceRuleset);
|
|
150
|
+
expect((red.importedRuleset as any).rules.sourceNode).toBe(sourceBody);
|
|
151
|
+
expect((blue.importedRuleset as any).rules.sourceNode).toBe(sourceBody);
|
|
152
|
+
expect(redDecl.sourceNode).toBe(sourceDecl);
|
|
153
|
+
expect(blueDecl.sourceNode).toBe(sourceDecl);
|
|
154
|
+
expect(red.evald.render(red.localContext)).toContain('color: red');
|
|
155
|
+
expect(blue.evald.render(blue.localContext)).toContain('color: blue');
|
|
156
|
+
expect(sourceRuleset.parent).toBe(sourceTree);
|
|
157
|
+
expect(sourceBody.parent).toBe(sourceRuleset);
|
|
158
|
+
expect(sourceDecl.parent).toBe(sourceBody);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('import returned trees already preserve descendant parent chains to the returned Rules', async () => {
|
|
162
|
+
const importedPath = resolve(process.cwd(), 'imported-parent-chain.jess');
|
|
163
|
+
context.sourceTrees.set(importedPath, rules([
|
|
164
|
+
ruleset({
|
|
165
|
+
selector: sellist([sel([el('.imported')])]),
|
|
166
|
+
rules: rules([
|
|
167
|
+
decl({ name: any('color'), value: any('red') })
|
|
168
|
+
])
|
|
169
|
+
})
|
|
170
|
+
]));
|
|
171
|
+
|
|
172
|
+
const node = rules([
|
|
173
|
+
style({
|
|
174
|
+
path: quoted(any('imported-parent-chain.jess'))
|
|
175
|
+
}, {
|
|
176
|
+
type: 'import'
|
|
177
|
+
})
|
|
178
|
+
]);
|
|
179
|
+
|
|
180
|
+
const evald = await node.eval(context);
|
|
181
|
+
const importedRules = evald.at(0, context) as Rules;
|
|
182
|
+
const importedRuleset = importedRules.at(0, context);
|
|
183
|
+
const importedDecl = (importedRuleset as any).rules.at(0, context);
|
|
184
|
+
const rootCtx = asRulesContext(context, evald);
|
|
185
|
+
const importedCtx = asRulesContext(context, importedRules);
|
|
186
|
+
|
|
187
|
+
expect(getParent(importedRules, rootCtx)).toBe(evald);
|
|
188
|
+
expect(getParent(importedRuleset, importedCtx)).toBe(importedRules);
|
|
189
|
+
expect(getParent((importedRuleset as any).rules, importedCtx)).toBe(importedRuleset);
|
|
190
|
+
expect(getParent(importedDecl, importedCtx)).toBe((importedRuleset as any).rules);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('compose type cannot see parent rules variables', async () => {
|
|
194
|
+
// Set up imported file
|
|
195
|
+
const composedPath = resolve(process.cwd(), 'composed.jess');
|
|
196
|
+
context.sourceTrees.set(composedPath, rules([
|
|
197
|
+
ruleset({
|
|
198
|
+
selector: sellist([sel([el('.composed')])]),
|
|
199
|
+
rules: rules([
|
|
200
|
+
decl({ name: any('color'), value: ref('parentVar', { type: 'variable', fallbackValue: any('blue') }) })
|
|
201
|
+
])
|
|
202
|
+
})
|
|
203
|
+
]));
|
|
204
|
+
|
|
205
|
+
// Parent file with variable
|
|
206
|
+
const parentVar = vardecl({ name: 'parentVar', value: any('red') });
|
|
207
|
+
const node = rules([
|
|
208
|
+
parentVar,
|
|
209
|
+
style({
|
|
210
|
+
path: quoted(any('composed.jess'))
|
|
211
|
+
}, {
|
|
212
|
+
type: 'compose',
|
|
213
|
+
namespace: '*'
|
|
214
|
+
})
|
|
215
|
+
]);
|
|
216
|
+
|
|
217
|
+
const evald = await node.eval(context);
|
|
218
|
+
const composedRules = evald.at(1, context) as Rules;
|
|
219
|
+
const composedRuleset = composedRules.at(0, context);
|
|
220
|
+
const composedCtx = asRulesContext(context, (composedRuleset as any).rules);
|
|
221
|
+
|
|
222
|
+
// The composed ruleset should NOT be able to reference the parent variable
|
|
223
|
+
// It should use the fallback value instead
|
|
224
|
+
const composedDecl = (composedRuleset as any).rules.at(0, composedCtx);
|
|
225
|
+
const resolved = await composedDecl.eval(composedCtx);
|
|
226
|
+
expect(resolved.toTrimmedString({ context: composedCtx })).toBe('color: blue');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('import type variables are visible to parent', async () => {
|
|
230
|
+
context.sourceTrees.set('imported.jess', rules([
|
|
231
|
+
vardecl({ name: 'importedVar', value: any('green') })
|
|
232
|
+
]));
|
|
233
|
+
|
|
234
|
+
const node = rules([
|
|
235
|
+
style({
|
|
236
|
+
path: quoted(any('imported.jess'))
|
|
237
|
+
}, {
|
|
238
|
+
type: 'import'
|
|
239
|
+
}),
|
|
240
|
+
ruleset({
|
|
241
|
+
selector: sellist([sel([el('.parent')])]),
|
|
242
|
+
rules: rules([
|
|
243
|
+
decl({ name: any('color'), value: ref('importedVar', { type: 'variable' }) })
|
|
244
|
+
])
|
|
245
|
+
})
|
|
246
|
+
]);
|
|
247
|
+
|
|
248
|
+
const evald = await node.eval(context);
|
|
249
|
+
const parentRuleset = evald.at(1, context);
|
|
250
|
+
const parentCtx = asRulesContext(context, (parentRuleset as any).rules);
|
|
251
|
+
const parentDecl = (parentRuleset as any).rules.at(0, parentCtx);
|
|
252
|
+
const resolved = await parentDecl.eval(parentCtx);
|
|
253
|
+
expect(resolved.toTrimmedString({ context: parentCtx })).toBe('color: green');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('compose type variables are visible to parent', async () => {
|
|
257
|
+
const composedPath = resolve(process.cwd(), 'composed.jess');
|
|
258
|
+
context.sourceTrees.set(composedPath, rules([
|
|
259
|
+
vardecl({ name: 'composedVar', value: any('purple') })
|
|
260
|
+
]));
|
|
261
|
+
|
|
262
|
+
const node = rules([
|
|
263
|
+
style({
|
|
264
|
+
path: quoted(any('composed.jess'))
|
|
265
|
+
}, {
|
|
266
|
+
type: 'compose',
|
|
267
|
+
namespace: '*'
|
|
268
|
+
}),
|
|
269
|
+
ruleset({
|
|
270
|
+
selector: sellist([sel([el('.parent')])]),
|
|
271
|
+
rules: rules([
|
|
272
|
+
decl({ name: any('color'), value: ref('composedVar', { type: 'variable' }) })
|
|
273
|
+
])
|
|
274
|
+
})
|
|
275
|
+
]);
|
|
276
|
+
|
|
277
|
+
const evald = await node.eval(context);
|
|
278
|
+
const parentRuleset = evald.at(1, context);
|
|
279
|
+
const parentCtx = asRulesContext(context, (parentRuleset as any).rules);
|
|
280
|
+
const parentDecl = (parentRuleset as any).rules.at(0, parentCtx);
|
|
281
|
+
const resolved = await parentDecl.eval(parentCtx);
|
|
282
|
+
// Should use composedVar from the compose
|
|
283
|
+
expect(resolved.toTrimmedString({ context: parentCtx })).toBe('color: purple');
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('mixin visibility', () => {
|
|
288
|
+
it('import type mixins are visible to parent', async () => {
|
|
289
|
+
context.sourceTrees.set('imported.jess', rules([
|
|
290
|
+
mixin({
|
|
291
|
+
name: any('importedMixin'),
|
|
292
|
+
rules: rules([
|
|
293
|
+
decl({ name: any('color'), value: any('blue') })
|
|
294
|
+
])
|
|
295
|
+
})
|
|
296
|
+
]));
|
|
297
|
+
|
|
298
|
+
const node = rules([
|
|
299
|
+
style({
|
|
300
|
+
path: quoted(any('imported.jess'))
|
|
301
|
+
}, {
|
|
302
|
+
type: 'import'
|
|
303
|
+
}),
|
|
304
|
+
ruleset({
|
|
305
|
+
selector: sellist([sel([el('.parent')])]),
|
|
306
|
+
rules: rules([
|
|
307
|
+
call({ name: ref('importedMixin', { type: 'mixin' }) })
|
|
308
|
+
])
|
|
309
|
+
})
|
|
310
|
+
]);
|
|
311
|
+
|
|
312
|
+
const evald = await node.eval(context);
|
|
313
|
+
const parentRuleset = evald.at(1, context);
|
|
314
|
+
const mixinCall = (parentRuleset as any).rules.at(0, context);
|
|
315
|
+
const resolved = await mixinCall.eval(context);
|
|
316
|
+
expect(resolved.toTrimmedString({ context })).toContainString('color: blue');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('compose type mixins are visible to parent', async () => {
|
|
320
|
+
const composedPath = resolve(process.cwd(), 'composed.jess');
|
|
321
|
+
context.sourceTrees.set(composedPath, rules([
|
|
322
|
+
mixin({
|
|
323
|
+
name: any('composedMixin'),
|
|
324
|
+
rules: rules([
|
|
325
|
+
decl({ name: any('color'), value: any('yellow') })
|
|
326
|
+
])
|
|
327
|
+
})
|
|
328
|
+
]));
|
|
329
|
+
|
|
330
|
+
const node = rules([
|
|
331
|
+
style({
|
|
332
|
+
path: quoted(any('composed.jess'))
|
|
333
|
+
}, {
|
|
334
|
+
type: 'compose',
|
|
335
|
+
namespace: '*'
|
|
336
|
+
}),
|
|
337
|
+
ruleset({
|
|
338
|
+
selector: sellist([sel([el('.parent')])]),
|
|
339
|
+
rules: rules([
|
|
340
|
+
call({ name: ref('composedMixin', { type: 'mixin' }) })
|
|
341
|
+
])
|
|
342
|
+
})
|
|
343
|
+
]);
|
|
344
|
+
|
|
345
|
+
const evald = await node.eval(context);
|
|
346
|
+
const parentRuleset = evald.at(1, context);
|
|
347
|
+
const mixinCall = (parentRuleset as any).rules.at(0, context);
|
|
348
|
+
const resolved = await mixinCall.eval(context);
|
|
349
|
+
expect(resolved.toTrimmedString({ context })).toContainString('color: yellow');
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('reference import makes mixins optional', async () => {
|
|
353
|
+
const referencedPath = resolve(process.cwd(), 'referenced.jess');
|
|
354
|
+
context.sourceTrees.set(referencedPath, rules([
|
|
355
|
+
mixin({
|
|
356
|
+
name: any('referencedMixin'),
|
|
357
|
+
rules: rules([
|
|
358
|
+
decl({ name: any('color'), value: any('white') })
|
|
359
|
+
])
|
|
360
|
+
})
|
|
361
|
+
]));
|
|
362
|
+
|
|
363
|
+
// First try to use it directly - should work
|
|
364
|
+
const node1 = rules([
|
|
365
|
+
style({
|
|
366
|
+
path: quoted(any('referenced.jess'))
|
|
367
|
+
}, {
|
|
368
|
+
type: 'import',
|
|
369
|
+
importOptions: { reference: true }
|
|
370
|
+
}),
|
|
371
|
+
ruleset({
|
|
372
|
+
selector: sellist([sel([el('.parent')])]),
|
|
373
|
+
rules: rules([
|
|
374
|
+
call({ name: ref('referencedMixin', { type: 'mixin' }) })
|
|
375
|
+
])
|
|
376
|
+
})
|
|
377
|
+
]);
|
|
378
|
+
|
|
379
|
+
const evald1 = await node1.eval(context);
|
|
380
|
+
const parentRuleset1 = evald1.at(1, context);
|
|
381
|
+
const mixinCall1 = (parentRuleset1 as any).rules.at(0, context);
|
|
382
|
+
const resolved1 = await mixinCall1.eval(context);
|
|
383
|
+
expect(resolved1.toTrimmedString({ context })).toContainString('color: white');
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
describe('ruleset visibility', () => {
|
|
388
|
+
it('import type rulesets are visible to parent', async () => {
|
|
389
|
+
context.sourceTrees.set('imported.jess', rules([
|
|
390
|
+
ruleset({
|
|
391
|
+
selector: sellist([sel([el('.imported')])]),
|
|
392
|
+
rules: rules([
|
|
393
|
+
decl({ name: any('color'), value: any('red') })
|
|
394
|
+
])
|
|
395
|
+
})
|
|
396
|
+
]));
|
|
397
|
+
|
|
398
|
+
const node = rules([
|
|
399
|
+
style({
|
|
400
|
+
path: quoted(any('imported.jess'))
|
|
401
|
+
}, {
|
|
402
|
+
type: 'import'
|
|
403
|
+
})
|
|
404
|
+
]);
|
|
405
|
+
|
|
406
|
+
const evald = await node.eval(context);
|
|
407
|
+
const importedRules = evald.at(0, context) as Rules;
|
|
408
|
+
const importedRuleset = importedRules.at(0, context);
|
|
409
|
+
expect(importedRuleset).toBeDefined();
|
|
410
|
+
expect(importedRuleset.toTrimmedString({ context })).toContainString('.imported');
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('non-mutable import makes rulesets private', async () => {
|
|
414
|
+
const protectedPath = resolve(process.cwd(), 'protected.jess');
|
|
415
|
+
context.sourceTrees.set(protectedPath, rules([
|
|
416
|
+
ruleset({
|
|
417
|
+
selector: sellist([sel([el('.protected')])]),
|
|
418
|
+
rules: rules([
|
|
419
|
+
decl({ name: any('color'), value: any('green') })
|
|
420
|
+
])
|
|
421
|
+
})
|
|
422
|
+
]));
|
|
423
|
+
|
|
424
|
+
const node = rules([
|
|
425
|
+
style({
|
|
426
|
+
path: quoted(any('protected.jess'))
|
|
427
|
+
}, {
|
|
428
|
+
type: 'import',
|
|
429
|
+
importOptions: { mutable: false }
|
|
430
|
+
})
|
|
431
|
+
]);
|
|
432
|
+
|
|
433
|
+
const evald = await node.eval(context);
|
|
434
|
+
const importedRules = evald.at(0, context) as Rules;
|
|
435
|
+
// Ruleset should still exist but be private
|
|
436
|
+
const protectedRuleset = importedRules.at(0, context);
|
|
437
|
+
expect(protectedRuleset).toBeDefined();
|
|
438
|
+
// But it should not be findable via registry lookup
|
|
439
|
+
const found = getRulesetWithContext(context, evald, '.protected');
|
|
440
|
+
expect(found).toBeUndefined();
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('reference import makes rulesets optional', async () => {
|
|
444
|
+
const referencedPath = resolve(process.cwd(), 'referenced.jess');
|
|
445
|
+
context.sourceTrees.set(referencedPath, rules([
|
|
446
|
+
ruleset({
|
|
447
|
+
selector: sellist([sel([el('.referenced')])]),
|
|
448
|
+
rules: rules([
|
|
449
|
+
decl({ name: any('color'), value: any('blue') })
|
|
450
|
+
])
|
|
451
|
+
})
|
|
452
|
+
]));
|
|
453
|
+
|
|
454
|
+
const node = rules([
|
|
455
|
+
style({
|
|
456
|
+
path: quoted(any('referenced.jess'))
|
|
457
|
+
}, {
|
|
458
|
+
type: 'import',
|
|
459
|
+
importOptions: { reference: true }
|
|
460
|
+
})
|
|
461
|
+
]);
|
|
462
|
+
|
|
463
|
+
const evald = await node.eval(context);
|
|
464
|
+
const importedRules = evald.at(0, context) as Rules;
|
|
465
|
+
const referencedRuleset = importedRules.at(0, context);
|
|
466
|
+
expect(referencedRuleset).toBeDefined();
|
|
467
|
+
// Optional means it's only considered if not found elsewhere
|
|
468
|
+
// This is mainly for extend behavior
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
describe('readonly behavior', () => {
|
|
473
|
+
it('compose type is readonly by default', async () => {
|
|
474
|
+
const composedPath = resolve(process.cwd(), 'composed.jess');
|
|
475
|
+
context.sourceTrees.set(composedPath, rules([
|
|
476
|
+
vardecl({ name: 'composedVar', value: any('initial') })
|
|
477
|
+
]));
|
|
478
|
+
|
|
479
|
+
const node = rules([
|
|
480
|
+
style({
|
|
481
|
+
path: quoted(any('composed.jess'))
|
|
482
|
+
}, {
|
|
483
|
+
type: 'compose',
|
|
484
|
+
namespace: '*'
|
|
485
|
+
}),
|
|
486
|
+
vardecl({ name: 'composedVar', value: any('modified') })
|
|
487
|
+
]);
|
|
488
|
+
|
|
489
|
+
// Should throw because we're trying to shadow a readonly variable at the same level
|
|
490
|
+
await expect(async () => {
|
|
491
|
+
await node.eval(context);
|
|
492
|
+
}).rejects.toThrowError('"composedVar" is readonly');
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('import type is NOT readonly by default', async () => {
|
|
496
|
+
context.sourceTrees.set('imported.jess', rules([
|
|
497
|
+
vardecl({ name: 'importedVar', value: any('initial') })
|
|
498
|
+
]));
|
|
499
|
+
|
|
500
|
+
const node = rules([
|
|
501
|
+
style({
|
|
502
|
+
path: quoted(any('imported.jess'))
|
|
503
|
+
}, {
|
|
504
|
+
type: 'import'
|
|
505
|
+
}),
|
|
506
|
+
vardecl({ name: 'importedVar', value: any('modified') })
|
|
507
|
+
]);
|
|
508
|
+
|
|
509
|
+
const evald = await node.eval(context);
|
|
510
|
+
const importedRules = evald.at(0, context) as Rules;
|
|
511
|
+
const varDecl = getVarWithContext(context, evald, 'importedVar');
|
|
512
|
+
|
|
513
|
+
// Should have modified value because it's not readonly
|
|
514
|
+
expect(varDecl).toBeDefined();
|
|
515
|
+
// The variable lookup should return the local variable (index 1) which wins over the imported variable (index 0)
|
|
516
|
+
// because local variables in the current Rules are treated as having the highest index (Number.MAX_SAFE_INTEGER)
|
|
517
|
+
expect(varDecl.toTrimmedString({ context })).toBe('$importedVar: modified');
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it.skip('readonly can be overridden for compose', async () => {
|
|
521
|
+
// Skipped: There may not be a syntactic way to set @-compose to readonly: false
|
|
522
|
+
const composedPath = resolve(process.cwd(), 'composed.jess');
|
|
523
|
+
context.sourceTrees.set(composedPath, rules([
|
|
524
|
+
vardecl({ name: 'composedVar', value: any('initial') })
|
|
525
|
+
]));
|
|
526
|
+
|
|
527
|
+
const node = rules([
|
|
528
|
+
style({
|
|
529
|
+
path: quoted(any('composed.jess'))
|
|
530
|
+
}, {
|
|
531
|
+
type: 'compose',
|
|
532
|
+
namespace: '*',
|
|
533
|
+
importOptions: { readonly: false }
|
|
534
|
+
}),
|
|
535
|
+
vardecl({ name: 'composedVar', value: any('modified') })
|
|
536
|
+
]);
|
|
537
|
+
|
|
538
|
+
const evald = await node.eval(context);
|
|
539
|
+
const varDecl = getVarWithContext(context, evald, 'composedVar');
|
|
540
|
+
|
|
541
|
+
// Should have modified value because readonly was overridden
|
|
542
|
+
expect(varDecl.toTrimmedString({ context })).toBe('$composedVar: modified');
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it('readonly can be set for import', async () => {
|
|
546
|
+
const importedPath = resolve(process.cwd(), 'imported.jess');
|
|
547
|
+
context.sourceTrees.set(importedPath, rules([
|
|
548
|
+
vardecl({ name: 'importedVar', value: any('initial') })
|
|
549
|
+
]));
|
|
550
|
+
|
|
551
|
+
const node = rules([
|
|
552
|
+
style({
|
|
553
|
+
path: quoted(any('imported.jess'))
|
|
554
|
+
}, {
|
|
555
|
+
type: 'import',
|
|
556
|
+
importOptions: { readonly: true }
|
|
557
|
+
}),
|
|
558
|
+
vardecl({ name: 'importedVar', value: any('modified') })
|
|
559
|
+
]);
|
|
560
|
+
|
|
561
|
+
// Should throw because we're trying to shadow a readonly variable at the same level
|
|
562
|
+
await expect(async () => {
|
|
563
|
+
await node.eval(context);
|
|
564
|
+
}).rejects.toThrowError('"importedVar" is readonly');
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
describe('forward behavior', () => {
|
|
569
|
+
it.skip('forwarded members are not visible locally, but are visible downstream', async () => {
|
|
570
|
+
const forwardedPath = resolve(process.cwd(), 'forwarded.jess');
|
|
571
|
+
context.sourceTrees.set(forwardedPath, rules([
|
|
572
|
+
vardecl({ name: 'forwardedVar', value: any('ok') })
|
|
573
|
+
]));
|
|
574
|
+
|
|
575
|
+
const forwarderPath = resolve(process.cwd(), 'forwarder.jess');
|
|
576
|
+
context.sourceTrees.set(forwarderPath, rules([
|
|
577
|
+
style(
|
|
578
|
+
{ path: quoted(any('forwarded.jess')) },
|
|
579
|
+
{ type: 'compose', namespace: '*', importOptions: { forward: true } }
|
|
580
|
+
)
|
|
581
|
+
// Nothing else in this module.
|
|
582
|
+
]));
|
|
583
|
+
|
|
584
|
+
// Evaluate forwarder module (as if compiling that file directly)
|
|
585
|
+
const evaldForwarder = await rules([
|
|
586
|
+
style({ path: quoted(any('forwarder.jess')) }, { type: 'import' })
|
|
587
|
+
]).eval(context);
|
|
588
|
+
|
|
589
|
+
// Locally (inside the forwarder module), forwarded members should NOT be visible.
|
|
590
|
+
const forwarderRules = evaldForwarder.at(0, context) as Rules;
|
|
591
|
+
const localLookup = getVarWithContext(context, forwarderRules, 'forwardedVar');
|
|
592
|
+
expect(localLookup).toBeUndefined();
|
|
593
|
+
|
|
594
|
+
// Downstream (a consumer importing the forwarder), forwarded members SHOULD be visible.
|
|
595
|
+
const consumer = rules([
|
|
596
|
+
style({ path: quoted(any('forwarder.jess')) }, { type: 'import' })
|
|
597
|
+
]);
|
|
598
|
+
const evaldConsumer = await consumer.eval(context);
|
|
599
|
+
const consumerImport = evaldConsumer.at(0, context) as Rules;
|
|
600
|
+
const consumerImportChildren = Array.from((consumerImport as any).value ?? []);
|
|
601
|
+
const nestedForward = consumerImportChildren[0];
|
|
602
|
+
const downstreamLookup = getVarWithContext(context, evaldConsumer, 'forwardedVar');
|
|
603
|
+
context.rulesContext = evaldConsumer;
|
|
604
|
+
const importLookupFromConsumerContext = consumerImport.find('declaration', 'forwardedVar', 'VarDeclaration', {
|
|
605
|
+
context,
|
|
606
|
+
searchParents: true
|
|
607
|
+
});
|
|
608
|
+
context.rulesContext = consumerImport;
|
|
609
|
+
const importLookupFromImportContext = consumerImport.find('declaration', 'forwardedVar', 'VarDeclaration', {
|
|
610
|
+
context,
|
|
611
|
+
searchParents: true
|
|
612
|
+
});
|
|
613
|
+
expect(downstreamLookup).toBeDefined();
|
|
614
|
+
expect(downstreamLookup.toTrimmedString({ context })).toBe('$forwardedVar: ok');
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
describe('with values', () => {
|
|
619
|
+
it('can inject variables with "with" type', async () => {
|
|
620
|
+
const libraryPath = resolve(process.cwd(), 'library.jess');
|
|
621
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
622
|
+
ruleset({
|
|
623
|
+
selector: sellist([sel([el('.box')])]),
|
|
624
|
+
rules: rules([
|
|
625
|
+
decl({ name: any('color'), value: ref('primaryColor', { type: 'variable' }) })
|
|
626
|
+
])
|
|
627
|
+
})
|
|
628
|
+
]));
|
|
629
|
+
|
|
630
|
+
const node = rules([
|
|
631
|
+
style({
|
|
632
|
+
path: quoted(any('library.jess')),
|
|
633
|
+
withNode: rules([
|
|
634
|
+
vardecl({ name: 'primaryColor', value: any('purple') })
|
|
635
|
+
]) as any,
|
|
636
|
+
withType: 'with'
|
|
637
|
+
}, {
|
|
638
|
+
type: 'compose',
|
|
639
|
+
namespace: '*'
|
|
640
|
+
})
|
|
641
|
+
]);
|
|
642
|
+
|
|
643
|
+
const evald = await node.eval(context);
|
|
644
|
+
const composedRules = evald.at(0, context) as Rules;
|
|
645
|
+
|
|
646
|
+
// Test 1: Verify injected variables are accessible
|
|
647
|
+
const injectedVar = getVarWithContext(context, composedRules, 'primaryColor');
|
|
648
|
+
expect(injectedVar).toBeDefined();
|
|
649
|
+
// The variable declaration exists, which means the injection worked
|
|
650
|
+
// We can verify the value by evaluating the variable's value property
|
|
651
|
+
const injectedVarValueNode = (injectedVar as any).value;
|
|
652
|
+
const injectedVarValue = await injectedVarValueNode.eval(context);
|
|
653
|
+
expect(injectedVarValue.toTrimmedString({ context })).toBe('purple');
|
|
654
|
+
|
|
655
|
+
// Test 2: Verify computed values based on injected variables are correct
|
|
656
|
+
// Find the ruleset and its declaration
|
|
657
|
+
const foundRuleset = Array.from(composedRules.value).find(
|
|
658
|
+
node => isNode(node, N.Ruleset)
|
|
659
|
+
);
|
|
660
|
+
expect(foundRuleset).toBeDefined();
|
|
661
|
+
const foundCtx = asRulesContext(context, (foundRuleset as any).rules);
|
|
662
|
+
const foundDecl = (foundRuleset as any).rules.at(0, foundCtx);
|
|
663
|
+
expect(foundDecl).toBeDefined();
|
|
664
|
+
const resolved = await foundDecl.eval(foundCtx);
|
|
665
|
+
expect(resolved.toTrimmedString({ context: foundCtx })).toBe('color: purple');
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
it('configured compose returned trees already preserve descendant parent chains to the returned Rules', async () => {
|
|
669
|
+
const libraryPath = resolve(process.cwd(), 'library-parent-chain.jess');
|
|
670
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
671
|
+
ruleset({
|
|
672
|
+
selector: sellist([sel([el('.box')])]),
|
|
673
|
+
rules: rules([
|
|
674
|
+
decl({ name: any('color'), value: any('purple') })
|
|
675
|
+
])
|
|
676
|
+
})
|
|
677
|
+
]));
|
|
678
|
+
|
|
679
|
+
const node = rules([
|
|
680
|
+
style({
|
|
681
|
+
path: quoted(any('library-parent-chain.jess')),
|
|
682
|
+
withNode: rules([
|
|
683
|
+
vardecl({ name: 'primaryColor', value: any('purple') })
|
|
684
|
+
]) as any,
|
|
685
|
+
withType: 'with'
|
|
686
|
+
}, {
|
|
687
|
+
type: 'compose',
|
|
688
|
+
namespace: '*'
|
|
689
|
+
})
|
|
690
|
+
]);
|
|
691
|
+
|
|
692
|
+
const evald = await node.eval(context);
|
|
693
|
+
const composedRules = evald.at(0, context) as Rules;
|
|
694
|
+
const foundRuleset = Array.from(composedRules.value).find(
|
|
695
|
+
node => isNode(node, N.Ruleset)
|
|
696
|
+
);
|
|
697
|
+
const foundDecl = (foundRuleset as any).rules.at(0, context);
|
|
698
|
+
const composedCtx = asRulesContext(context, composedRules);
|
|
699
|
+
|
|
700
|
+
expect(getParent(foundRuleset!, composedCtx)).toBe(composedRules);
|
|
701
|
+
expect(getParent((foundRuleset as any).rules, composedCtx)).toBe(foundRuleset);
|
|
702
|
+
expect(getParent(foundDecl, composedCtx)).toBe((foundRuleset as any).rules);
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
it('can inject variables with "set" type', async () => {
|
|
706
|
+
const libraryPath = resolve(process.cwd(), 'library.jess');
|
|
707
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
708
|
+
ruleset({
|
|
709
|
+
selector: sellist([sel([el('.box')])]),
|
|
710
|
+
rules: rules([
|
|
711
|
+
decl({ name: any('color'), value: ref('primaryColor', { type: 'variable' }) })
|
|
712
|
+
])
|
|
713
|
+
})
|
|
714
|
+
]));
|
|
715
|
+
|
|
716
|
+
const node = rules([
|
|
717
|
+
style({
|
|
718
|
+
path: quoted(any('library.jess')),
|
|
719
|
+
withNode: rules([
|
|
720
|
+
vardecl({ name: 'primaryColor', value: any('orange') })
|
|
721
|
+
]) as any,
|
|
722
|
+
withType: 'set'
|
|
723
|
+
}, {
|
|
724
|
+
type: 'compose',
|
|
725
|
+
namespace: '*'
|
|
726
|
+
})
|
|
727
|
+
]);
|
|
728
|
+
|
|
729
|
+
const evald = await node.eval(context);
|
|
730
|
+
const composedRules = evald.at(0, context) as Rules;
|
|
731
|
+
|
|
732
|
+
// Test 1: Verify injected variables are accessible
|
|
733
|
+
const injectedVar = getVarWithContext(context, composedRules, 'primaryColor');
|
|
734
|
+
expect(injectedVar).toBeDefined();
|
|
735
|
+
// The variable declaration exists, which means the injection worked
|
|
736
|
+
// We can verify the value by evaluating the variable's value property
|
|
737
|
+
const injectedVarValueNode = (injectedVar as any).value;
|
|
738
|
+
const injectedVarValue = await injectedVarValueNode.eval(context);
|
|
739
|
+
expect(injectedVarValue.toTrimmedString({ context })).toBe('orange');
|
|
740
|
+
|
|
741
|
+
// Test 2: Verify computed values based on injected variables are correct
|
|
742
|
+
// Find the ruleset and its declaration
|
|
743
|
+
const foundRuleset = Array.from(composedRules.value).find(
|
|
744
|
+
node => isNode(node, N.Ruleset)
|
|
745
|
+
);
|
|
746
|
+
expect(foundRuleset).toBeDefined();
|
|
747
|
+
const foundCtx = asRulesContext(context, (foundRuleset as any).rules);
|
|
748
|
+
const foundDecl = (foundRuleset as any).rules.at(0, foundCtx);
|
|
749
|
+
expect(foundDecl).toBeDefined();
|
|
750
|
+
const resolved = await foundDecl.eval(foundCtx);
|
|
751
|
+
expect(resolved.toTrimmedString({ context: foundCtx })).toBe('color: orange');
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
it('updates computed variables with "with" type - scope lookup', async () => {
|
|
755
|
+
// Test that when we inject a variable, dependent variables are updated
|
|
756
|
+
// This tests scope-based lookup ($var)
|
|
757
|
+
const libraryPath = resolve(process.cwd(), 'library.jess');
|
|
758
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
759
|
+
vardecl({ name: 'baseColor', value: any('red') }),
|
|
760
|
+
vardecl({ name: 'derivedColor', value: ref('baseColor', { type: 'variable' }) })
|
|
761
|
+
]));
|
|
762
|
+
|
|
763
|
+
const node = rules([
|
|
764
|
+
style({
|
|
765
|
+
path: quoted(any('library.jess')),
|
|
766
|
+
withNode: rules([
|
|
767
|
+
vardecl({ name: 'baseColor', value: any('blue') })
|
|
768
|
+
]) as any,
|
|
769
|
+
withType: 'with'
|
|
770
|
+
}, {
|
|
771
|
+
type: 'compose',
|
|
772
|
+
namespace: '*'
|
|
773
|
+
})
|
|
774
|
+
]);
|
|
775
|
+
|
|
776
|
+
const evald = await node.eval(context);
|
|
777
|
+
const composedRules = evald.at(0, context) as Rules;
|
|
778
|
+
|
|
779
|
+
// Verify baseColor has the injected value
|
|
780
|
+
const baseColor = getVarWithContext(context, composedRules, 'baseColor');
|
|
781
|
+
expect(baseColor).toBeDefined();
|
|
782
|
+
const baseColorValue = await (baseColor as any).value.eval(context);
|
|
783
|
+
expect(baseColorValue.toTrimmedString({ context })).toBe('blue');
|
|
784
|
+
|
|
785
|
+
// Verify derivedColor reflects the injected value (scope lookup)
|
|
786
|
+
const derivedColor = getVarWithContext(context, composedRules, 'derivedColor');
|
|
787
|
+
expect(derivedColor).toBeDefined();
|
|
788
|
+
const derivedColorValue = await (derivedColor as any).value.eval(context);
|
|
789
|
+
expect(derivedColorValue.toTrimmedString({ context })).toBe('blue');
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
it('updates computed variables with "with" type - linear lookup', async () => {
|
|
793
|
+
// Test that when we inject a variable, dependent variables are updated
|
|
794
|
+
// This tests linear lookup ($^var)
|
|
795
|
+
const libraryPath = resolve(process.cwd(), 'library.jess');
|
|
796
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
797
|
+
vardecl({ name: 'baseColor', value: any('red') }),
|
|
798
|
+
vardecl({ name: 'derivedColor', value: ref('baseColor', { type: 'variable', resolution: 'linear' }) })
|
|
799
|
+
]));
|
|
800
|
+
|
|
801
|
+
const node = rules([
|
|
802
|
+
style({
|
|
803
|
+
path: quoted(any('library.jess')),
|
|
804
|
+
withNode: rules([
|
|
805
|
+
vardecl({ name: 'baseColor', value: any('green') })
|
|
806
|
+
]) as any,
|
|
807
|
+
withType: 'with'
|
|
808
|
+
}, {
|
|
809
|
+
type: 'compose',
|
|
810
|
+
namespace: '*'
|
|
811
|
+
})
|
|
812
|
+
]);
|
|
813
|
+
|
|
814
|
+
const evald = await node.eval(context);
|
|
815
|
+
const composedRules = evald.at(0, context) as Rules;
|
|
816
|
+
|
|
817
|
+
// Verify baseColor has the injected value
|
|
818
|
+
const baseColor = getVarWithContext(context, composedRules, 'baseColor');
|
|
819
|
+
expect(baseColor).toBeDefined();
|
|
820
|
+
const baseColorValue = await (baseColor as any).value.eval(context);
|
|
821
|
+
expect(baseColorValue.toTrimmedString({ context })).toBe('green');
|
|
822
|
+
|
|
823
|
+
// Verify derivedColor reflects the injected value (linear lookup)
|
|
824
|
+
const derivedColor = getVarWithContext(context, composedRules, 'derivedColor');
|
|
825
|
+
expect(derivedColor).toBeDefined();
|
|
826
|
+
const derivedColorValue = await (derivedColor as any).value.eval(context);
|
|
827
|
+
expect(derivedColorValue.toTrimmedString({ context })).toBe('green');
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
it('updates computed variables with "set" type - scope lookup', async () => {
|
|
831
|
+
// Test that when we inject a variable with "set", dependent variables are updated
|
|
832
|
+
// This tests scope-based lookup ($var)
|
|
833
|
+
const libraryPath = resolve(process.cwd(), 'library.jess');
|
|
834
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
835
|
+
vardecl({ name: 'baseColor', value: any('red') }),
|
|
836
|
+
vardecl({ name: 'derivedColor', value: ref('baseColor', { type: 'variable' }) })
|
|
837
|
+
]));
|
|
838
|
+
|
|
839
|
+
const node = rules([
|
|
840
|
+
style({
|
|
841
|
+
path: quoted(any('library.jess')),
|
|
842
|
+
withNode: rules([
|
|
843
|
+
vardecl({ name: 'baseColor', value: any('yellow') })
|
|
844
|
+
]) as any,
|
|
845
|
+
withType: 'set'
|
|
846
|
+
}, {
|
|
847
|
+
type: 'compose',
|
|
848
|
+
namespace: '*'
|
|
849
|
+
})
|
|
850
|
+
]);
|
|
851
|
+
|
|
852
|
+
const evald = await node.eval(context);
|
|
853
|
+
const composedRules = evald.at(0, context) as Rules;
|
|
854
|
+
|
|
855
|
+
// Verify baseColor has the injected value
|
|
856
|
+
const baseColor = getVarWithContext(context, composedRules, 'baseColor');
|
|
857
|
+
expect(baseColor).toBeDefined();
|
|
858
|
+
const baseColorValue = await (baseColor as any).value.eval(context);
|
|
859
|
+
expect(baseColorValue.toTrimmedString({ context })).toBe('yellow');
|
|
860
|
+
|
|
861
|
+
// Verify derivedColor reflects the injected value (scope lookup)
|
|
862
|
+
const derivedColor = getVarWithContext(context, composedRules, 'derivedColor');
|
|
863
|
+
expect(derivedColor).toBeDefined();
|
|
864
|
+
const derivedColorValue = await (derivedColor as any).value.eval(context);
|
|
865
|
+
expect(derivedColorValue.toTrimmedString({ context })).toBe('yellow');
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
it('updates computed variables with "set" type - linear lookup', async () => {
|
|
869
|
+
// Test that when we inject a variable with "set", dependent variables are updated
|
|
870
|
+
// This tests linear lookup ($^var)
|
|
871
|
+
const libraryPath = resolve(process.cwd(), 'library.jess');
|
|
872
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
873
|
+
vardecl({ name: 'baseColor', value: any('red') }),
|
|
874
|
+
vardecl({ name: 'derivedColor', value: ref('baseColor', { type: 'variable', resolution: 'linear' }) })
|
|
875
|
+
]));
|
|
876
|
+
|
|
877
|
+
const node = rules([
|
|
878
|
+
style({
|
|
879
|
+
path: quoted(any('library.jess')),
|
|
880
|
+
withNode: rules([
|
|
881
|
+
vardecl({ name: 'baseColor', value: any('cyan') })
|
|
882
|
+
]) as any,
|
|
883
|
+
withType: 'set'
|
|
884
|
+
}, {
|
|
885
|
+
type: 'compose',
|
|
886
|
+
namespace: '*'
|
|
887
|
+
})
|
|
888
|
+
]);
|
|
889
|
+
|
|
890
|
+
const evald = await node.eval(context);
|
|
891
|
+
const composedRules = evald.at(0, context) as Rules;
|
|
892
|
+
|
|
893
|
+
// When 'set' is used, structure is flattened:
|
|
894
|
+
// [new injected variables (not found), ...all nodes from imported rules (with replacements)]
|
|
895
|
+
// All variables are in the same Rules scope
|
|
896
|
+
|
|
897
|
+
// Verify baseColor was injected (should be the injected one, not the original)
|
|
898
|
+
const baseColor = getVarWithContext(context, composedRules, 'baseColor');
|
|
899
|
+
expect(baseColor).toBeDefined();
|
|
900
|
+
const baseColorValue = await (baseColor as any).value.eval(context);
|
|
901
|
+
expect(baseColorValue.toTrimmedString({ context })).toBe('cyan');
|
|
902
|
+
|
|
903
|
+
// Verify derivedColor reflects the injected value (linear lookup)
|
|
904
|
+
// derivedColor is in the composed rules (flattened structure)
|
|
905
|
+
// It should be able to find the injected baseColor in the same scope
|
|
906
|
+
const derivedColor = getVarWithContext(context, composedRules, 'derivedColor');
|
|
907
|
+
expect(derivedColor).toBeDefined();
|
|
908
|
+
// The value should already be evaluated during the import evaluation
|
|
909
|
+
// and should have used the injected baseColor
|
|
910
|
+
const derivedColorValue = await (derivedColor as any).value.eval(context);
|
|
911
|
+
expect(derivedColorValue.toTrimmedString({ context })).toBe('cyan');
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
it('throws if "set" is used more than once', async () => {
|
|
915
|
+
const libraryPath = resolve(process.cwd(), 'library.jess');
|
|
916
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
917
|
+
vardecl({ name: 'var', value: any('value') })
|
|
918
|
+
]));
|
|
919
|
+
|
|
920
|
+
// First use
|
|
921
|
+
const node1 = rules([
|
|
922
|
+
style({
|
|
923
|
+
path: quoted(any('library.jess')),
|
|
924
|
+
withNode: rules([
|
|
925
|
+
vardecl({ name: 'var', value: any('first') })
|
|
926
|
+
]) as any,
|
|
927
|
+
withType: 'set'
|
|
928
|
+
}, {
|
|
929
|
+
type: 'compose',
|
|
930
|
+
namespace: '*'
|
|
931
|
+
})
|
|
932
|
+
]);
|
|
933
|
+
await node1.eval(context);
|
|
934
|
+
|
|
935
|
+
// Second use - should throw
|
|
936
|
+
const node2 = rules([
|
|
937
|
+
style({
|
|
938
|
+
path: quoted(any('library.jess')),
|
|
939
|
+
withNode: rules([
|
|
940
|
+
vardecl({ name: 'var', value: any('second') })
|
|
941
|
+
]) as any,
|
|
942
|
+
withType: 'set'
|
|
943
|
+
}, {
|
|
944
|
+
type: 'compose',
|
|
945
|
+
namespace: '*'
|
|
946
|
+
})
|
|
947
|
+
]);
|
|
948
|
+
|
|
949
|
+
await expect(async () => {
|
|
950
|
+
await node2.eval(context);
|
|
951
|
+
}).rejects.toThrow('Cannot configure a stylesheet more than once');
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
it('two sequential "with" imports resolve independently via rendered output', async () => {
|
|
955
|
+
const libraryPath = resolve(process.cwd(), 'library-with-seq.jess');
|
|
956
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
957
|
+
vardecl({ name: 'baseColor', value: any('red') }),
|
|
958
|
+
ruleset({
|
|
959
|
+
selector: sellist([sel([el('.box')])]),
|
|
960
|
+
rules: rules([
|
|
961
|
+
decl({ name: any('color'), value: ref('baseColor', { type: 'variable' }) })
|
|
962
|
+
])
|
|
963
|
+
})
|
|
964
|
+
]));
|
|
965
|
+
|
|
966
|
+
// First import: baseColor = blue
|
|
967
|
+
const node1 = rules([
|
|
968
|
+
style({
|
|
969
|
+
path: quoted(any('library-with-seq.jess')),
|
|
970
|
+
withNode: rules([
|
|
971
|
+
vardecl({ name: 'baseColor', value: any('blue') })
|
|
972
|
+
]) as any,
|
|
973
|
+
withType: 'with'
|
|
974
|
+
}, { type: 'compose', namespace: '*' })
|
|
975
|
+
]);
|
|
976
|
+
const evald1 = await node1.eval(context);
|
|
977
|
+
expect(evald1.render(context)).toContain('color: blue');
|
|
978
|
+
|
|
979
|
+
// Second import (fresh context): baseColor = green — must not see blue
|
|
980
|
+
const context2 = createTestContext();
|
|
981
|
+
context2.sourceTrees.set(libraryPath, context.sourceTrees.get(libraryPath)!);
|
|
982
|
+
const node2 = rules([
|
|
983
|
+
style({
|
|
984
|
+
path: quoted(any('library-with-seq.jess')),
|
|
985
|
+
withNode: rules([
|
|
986
|
+
vardecl({ name: 'baseColor', value: any('green') })
|
|
987
|
+
]) as any,
|
|
988
|
+
withType: 'with'
|
|
989
|
+
}, { type: 'compose', namespace: '*' })
|
|
990
|
+
]);
|
|
991
|
+
const evald2 = await node2.eval(context2);
|
|
992
|
+
expect(evald2.render(context2)).toContain('color: green');
|
|
993
|
+
expect(evald2.render(context2)).not.toContain('color: blue');
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
it('configured compose placements can reuse canonical imported rulesets while rendering with different values', async () => {
|
|
997
|
+
const libraryPath = resolve(process.cwd(), 'library-with-canonical-reuse.jess');
|
|
998
|
+
const sourceRuleset = ruleset({
|
|
999
|
+
selector: sellist([sel([el('.box')])]),
|
|
1000
|
+
rules: rules([
|
|
1001
|
+
decl({ name: any('color'), value: ref('baseColor', { type: 'variable' }) })
|
|
1002
|
+
])
|
|
1003
|
+
});
|
|
1004
|
+
const sourceBody = sourceRuleset.get('rules');
|
|
1005
|
+
const sourceDecl = sourceBody.at(0, context);
|
|
1006
|
+
const sourceTree = rules([
|
|
1007
|
+
vardecl({ name: 'baseColor', value: any('red') }),
|
|
1008
|
+
sourceRuleset
|
|
1009
|
+
]);
|
|
1010
|
+
|
|
1011
|
+
const evaluateCompose = async (color: string) => {
|
|
1012
|
+
const localContext = createTestContext();
|
|
1013
|
+
localContext.sourceTrees.set(libraryPath, sourceTree);
|
|
1014
|
+
const entry = rules([
|
|
1015
|
+
style({
|
|
1016
|
+
path: quoted(any('library-with-canonical-reuse.jess')),
|
|
1017
|
+
withNode: rules([
|
|
1018
|
+
vardecl({ name: 'baseColor', value: any(color) })
|
|
1019
|
+
]) as any,
|
|
1020
|
+
withType: 'with'
|
|
1021
|
+
}, { type: 'compose', namespace: '*' })
|
|
1022
|
+
]);
|
|
1023
|
+
const evald = await entry.eval(localContext);
|
|
1024
|
+
const importedRules = evald.at(0, localContext) as Rules;
|
|
1025
|
+
const importedRuleset = Array.from(importedRules.value).find(node => isNode(node, N.Ruleset)) as Ruleset;
|
|
1026
|
+
return { localContext, evald, importedRules, importedRuleset };
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
const blue = await evaluateCompose('blue');
|
|
1030
|
+
const green = await evaluateCompose('green');
|
|
1031
|
+
|
|
1032
|
+
const blueDecl = (blue.importedRuleset as any).rules.at(0, blue.localContext);
|
|
1033
|
+
const greenDecl = (green.importedRuleset as any).rules.at(0, green.localContext);
|
|
1034
|
+
|
|
1035
|
+
expect(blue.importedRuleset).not.toBe(green.importedRuleset);
|
|
1036
|
+
expect(blue.importedRuleset.sourceNode).toBe(sourceRuleset);
|
|
1037
|
+
expect(green.importedRuleset.sourceNode).toBe(sourceRuleset);
|
|
1038
|
+
expect((blue.importedRuleset as any).rules.sourceNode).toBe(sourceBody);
|
|
1039
|
+
expect((green.importedRuleset as any).rules.sourceNode).toBe(sourceBody);
|
|
1040
|
+
expect(blueDecl.sourceNode).toBe(sourceDecl);
|
|
1041
|
+
expect(greenDecl.sourceNode).toBe(sourceDecl);
|
|
1042
|
+
expect(blue.evald.render(blue.localContext)).toContain('color: blue');
|
|
1043
|
+
expect(green.evald.render(green.localContext)).toContain('color: green');
|
|
1044
|
+
expect(sourceRuleset.parent).toBe(sourceTree);
|
|
1045
|
+
expect(sourceBody.parent).toBe(sourceRuleset);
|
|
1046
|
+
expect(sourceDecl.parent).toBe(sourceBody);
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
it('with replaces existing root vars and nested rules resolve the replacement', async () => {
|
|
1050
|
+
const libraryPath = resolve(process.cwd(), 'library-with-replace-root-var.jess');
|
|
1051
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
1052
|
+
vardecl({ name: 'baseColor', value: any('red') }),
|
|
1053
|
+
ruleset({
|
|
1054
|
+
selector: sellist([sel([el('.box')])]),
|
|
1055
|
+
rules: rules([
|
|
1056
|
+
decl({ name: any('color'), value: ref('baseColor', { type: 'variable' }) })
|
|
1057
|
+
])
|
|
1058
|
+
})
|
|
1059
|
+
]));
|
|
1060
|
+
|
|
1061
|
+
const node = rules([
|
|
1062
|
+
style({
|
|
1063
|
+
path: quoted(any('library-with-replace-root-var.jess')),
|
|
1064
|
+
withNode: rules([
|
|
1065
|
+
vardecl({ name: 'baseColor', value: any('blue') })
|
|
1066
|
+
]) as any,
|
|
1067
|
+
withType: 'with'
|
|
1068
|
+
}, { type: 'compose', namespace: '*' })
|
|
1069
|
+
]);
|
|
1070
|
+
|
|
1071
|
+
const evald = await node.eval(context);
|
|
1072
|
+
expect(evald.render(context)).toContain('color: blue');
|
|
1073
|
+
expect(evald.render(context)).not.toContain('color: red');
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
it('with injects missing vars and nested rules can reference them during evaluation', async () => {
|
|
1077
|
+
const libraryPath = resolve(process.cwd(), 'library-with-inject-var.jess');
|
|
1078
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
1079
|
+
ruleset({
|
|
1080
|
+
selector: sellist([sel([el('.box')])]),
|
|
1081
|
+
rules: rules([
|
|
1082
|
+
decl({ name: any('color'), value: ref('injectedColor', { type: 'variable' }) })
|
|
1083
|
+
])
|
|
1084
|
+
})
|
|
1085
|
+
]));
|
|
1086
|
+
|
|
1087
|
+
const node = rules([
|
|
1088
|
+
style({
|
|
1089
|
+
path: quoted(any('library-with-inject-var.jess')),
|
|
1090
|
+
withNode: rules([
|
|
1091
|
+
vardecl({ name: 'injectedColor', value: any('purple') })
|
|
1092
|
+
]) as any,
|
|
1093
|
+
withType: 'with'
|
|
1094
|
+
}, { type: 'compose', namespace: '*' })
|
|
1095
|
+
]);
|
|
1096
|
+
|
|
1097
|
+
const evald = await node.eval(context);
|
|
1098
|
+
expect(evald.render(context)).toContain('color: purple');
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
it('set replaces existing root vars and nested rules resolve the replacement', async () => {
|
|
1102
|
+
const libraryPath = resolve(process.cwd(), 'library-set-replace-root-var.jess');
|
|
1103
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
1104
|
+
vardecl({ name: 'baseColor', value: any('red') }),
|
|
1105
|
+
ruleset({
|
|
1106
|
+
selector: sellist([sel([el('.box')])]),
|
|
1107
|
+
rules: rules([
|
|
1108
|
+
decl({ name: any('color'), value: ref('baseColor', { type: 'variable' }) })
|
|
1109
|
+
])
|
|
1110
|
+
})
|
|
1111
|
+
]));
|
|
1112
|
+
|
|
1113
|
+
const node = rules([
|
|
1114
|
+
style({
|
|
1115
|
+
path: quoted(any('library-set-replace-root-var.jess')),
|
|
1116
|
+
withNode: rules([
|
|
1117
|
+
vardecl({ name: 'baseColor', value: any('orange') })
|
|
1118
|
+
]) as any,
|
|
1119
|
+
withType: 'set'
|
|
1120
|
+
}, { type: 'compose', namespace: '*' })
|
|
1121
|
+
]);
|
|
1122
|
+
|
|
1123
|
+
const evald = await node.eval(context);
|
|
1124
|
+
expect(evald.render(context)).toContain('color: orange');
|
|
1125
|
+
expect(evald.render(context)).not.toContain('color: red');
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
it('set injects missing vars and nested rules can reference them during evaluation', async () => {
|
|
1129
|
+
const libraryPath = resolve(process.cwd(), 'library-set-inject-var.jess');
|
|
1130
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
1131
|
+
ruleset({
|
|
1132
|
+
selector: sellist([sel([el('.box')])]),
|
|
1133
|
+
rules: rules([
|
|
1134
|
+
decl({ name: any('color'), value: ref('injectedColor', { type: 'variable' }) })
|
|
1135
|
+
])
|
|
1136
|
+
})
|
|
1137
|
+
]));
|
|
1138
|
+
|
|
1139
|
+
const node = rules([
|
|
1140
|
+
style({
|
|
1141
|
+
path: quoted(any('library-set-inject-var.jess')),
|
|
1142
|
+
withNode: rules([
|
|
1143
|
+
vardecl({ name: 'injectedColor', value: any('teal') })
|
|
1144
|
+
]) as any,
|
|
1145
|
+
withType: 'set'
|
|
1146
|
+
}, { type: 'compose', namespace: '*' })
|
|
1147
|
+
]);
|
|
1148
|
+
|
|
1149
|
+
const evald = await node.eval(context);
|
|
1150
|
+
expect(evald.render(context)).toContain('color: teal');
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
it('set values become the baseline for later compose imports of the same file', async () => {
|
|
1154
|
+
const libraryPath = resolve(process.cwd(), 'library-set-baseline.jess');
|
|
1155
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
1156
|
+
vardecl({ name: 'baseColor', value: any('red') }),
|
|
1157
|
+
ruleset({
|
|
1158
|
+
selector: sellist([sel([el('.box')])]),
|
|
1159
|
+
rules: rules([
|
|
1160
|
+
decl({ name: any('color'), value: ref('baseColor', { type: 'variable' }) })
|
|
1161
|
+
])
|
|
1162
|
+
})
|
|
1163
|
+
]));
|
|
1164
|
+
|
|
1165
|
+
const node = rules([
|
|
1166
|
+
style({
|
|
1167
|
+
path: quoted(any('library-set-baseline.jess')),
|
|
1168
|
+
withNode: rules([
|
|
1169
|
+
vardecl({ name: 'baseColor', value: any('blue') })
|
|
1170
|
+
]) as any,
|
|
1171
|
+
withType: 'set'
|
|
1172
|
+
}, { type: 'compose', namespace: '*' }),
|
|
1173
|
+
style({
|
|
1174
|
+
path: quoted(any('library-set-baseline.jess'))
|
|
1175
|
+
}, {
|
|
1176
|
+
type: 'compose',
|
|
1177
|
+
namespace: '*',
|
|
1178
|
+
importOptions: { multiple: true }
|
|
1179
|
+
})
|
|
1180
|
+
]);
|
|
1181
|
+
|
|
1182
|
+
const evald = await node.eval(context);
|
|
1183
|
+
const first = evald.at(0, context) as Rules;
|
|
1184
|
+
const second = evald.at(1, context) as Rules;
|
|
1185
|
+
|
|
1186
|
+
const firstBaseColor = getVarWithContext(context, first, 'baseColor');
|
|
1187
|
+
const secondBaseColor = getVarWithContext(context, second, 'baseColor');
|
|
1188
|
+
|
|
1189
|
+
expect((await (firstBaseColor as any).value.eval(context)).toTrimmedString({ context })).toBe('blue');
|
|
1190
|
+
expect((await (secondBaseColor as any).value.eval(context)).toTrimmedString({ context })).toBe('blue');
|
|
1191
|
+
expect(second.render(context)).toContain('color: blue');
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
it('with can override a prior set baseline for the same file', async () => {
|
|
1195
|
+
const libraryPath = resolve(process.cwd(), 'library-set-with-override.jess');
|
|
1196
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
1197
|
+
vardecl({ name: 'baseColor', value: any('red') }),
|
|
1198
|
+
ruleset({
|
|
1199
|
+
selector: sellist([sel([el('.box')])]),
|
|
1200
|
+
rules: rules([
|
|
1201
|
+
decl({ name: any('color'), value: ref('baseColor', { type: 'variable' }) })
|
|
1202
|
+
])
|
|
1203
|
+
})
|
|
1204
|
+
]));
|
|
1205
|
+
|
|
1206
|
+
const node = rules([
|
|
1207
|
+
style({
|
|
1208
|
+
path: quoted(any('library-set-with-override.jess')),
|
|
1209
|
+
withNode: rules([
|
|
1210
|
+
vardecl({ name: 'baseColor', value: any('blue') })
|
|
1211
|
+
]) as any,
|
|
1212
|
+
withType: 'set'
|
|
1213
|
+
}, { type: 'compose', namespace: '*' }),
|
|
1214
|
+
style({
|
|
1215
|
+
path: quoted(any('library-set-with-override.jess')),
|
|
1216
|
+
withNode: rules([
|
|
1217
|
+
vardecl({ name: 'baseColor', value: any('green') })
|
|
1218
|
+
]) as any,
|
|
1219
|
+
withType: 'with'
|
|
1220
|
+
}, {
|
|
1221
|
+
type: 'compose',
|
|
1222
|
+
namespace: '*',
|
|
1223
|
+
importOptions: { multiple: true }
|
|
1224
|
+
})
|
|
1225
|
+
]);
|
|
1226
|
+
|
|
1227
|
+
const evald = await node.eval(context);
|
|
1228
|
+
const first = evald.at(0, context) as Rules;
|
|
1229
|
+
const second = evald.at(1, context) as Rules;
|
|
1230
|
+
|
|
1231
|
+
const firstBaseColor = getVarWithContext(context, first, 'baseColor');
|
|
1232
|
+
const secondBaseColor = getVarWithContext(context, second, 'baseColor');
|
|
1233
|
+
|
|
1234
|
+
expect((await (firstBaseColor as any).value.eval(context)).toTrimmedString({ context })).toBe('blue');
|
|
1235
|
+
expect((await (secondBaseColor as any).value.eval(context)).toTrimmedString({ context })).toBe('green');
|
|
1236
|
+
expect(first.render(context)).toContain('color: blue');
|
|
1237
|
+
expect(second.render(context)).toContain('color: green');
|
|
1238
|
+
});
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
describe('multiple imports', () => {
|
|
1242
|
+
it('import type can be imported multiple times', async () => {
|
|
1243
|
+
context.sourceTrees.set('imported.jess', rules([
|
|
1244
|
+
ruleset({
|
|
1245
|
+
selector: sellist([sel([el('.imported')])]),
|
|
1246
|
+
rules: rules([
|
|
1247
|
+
decl({ name: any('color'), value: any('red') })
|
|
1248
|
+
])
|
|
1249
|
+
})
|
|
1250
|
+
]));
|
|
1251
|
+
|
|
1252
|
+
const node = rules([
|
|
1253
|
+
style({
|
|
1254
|
+
path: quoted(any('imported.jess'))
|
|
1255
|
+
}, {
|
|
1256
|
+
type: 'import'
|
|
1257
|
+
}),
|
|
1258
|
+
style({
|
|
1259
|
+
path: quoted(any('imported.jess'))
|
|
1260
|
+
}, {
|
|
1261
|
+
type: 'import',
|
|
1262
|
+
importOptions: { multiple: true }
|
|
1263
|
+
})
|
|
1264
|
+
]);
|
|
1265
|
+
|
|
1266
|
+
const evald = await node.eval(context);
|
|
1267
|
+
// Both imports should be present
|
|
1268
|
+
expect(evald.value.length).toBe(2);
|
|
1269
|
+
});
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
describe('less import fixture regressions', () => {
|
|
1273
|
+
it('import-inline: inline import with media postlude inlines source content', async () => {
|
|
1274
|
+
const inlinePath = resolve(process.cwd(), 'inline-source.css');
|
|
1275
|
+
const inlineContext = new Context({}, [{
|
|
1276
|
+
name: 'inline-plugin',
|
|
1277
|
+
supportedExtensions: ['.css'],
|
|
1278
|
+
resolve(filePath: string | string[]) {
|
|
1279
|
+
const paths = Array.isArray(filePath) ? filePath : [filePath];
|
|
1280
|
+
return paths.map(p => (p.endsWith('.css') ? p : `${p}.css`));
|
|
1281
|
+
},
|
|
1282
|
+
locate(pathCandidates: string[], currentDir: string) {
|
|
1283
|
+
for (const candidate of pathCandidates) {
|
|
1284
|
+
const abs = candidate.startsWith('/') ? candidate : resolve(currentDir, candidate);
|
|
1285
|
+
if (abs === inlinePath) {
|
|
1286
|
+
return abs;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
return null;
|
|
1290
|
+
},
|
|
1291
|
+
async getSource() {
|
|
1292
|
+
return '#css { color: yellow; }\n';
|
|
1293
|
+
}
|
|
1294
|
+
}]);
|
|
1295
|
+
inlineContext.treeContext = {
|
|
1296
|
+
file: { name: 'entry.less', path: process.cwd(), fullPath: resolve(process.cwd(), 'entry.less') }
|
|
1297
|
+
} as any;
|
|
1298
|
+
|
|
1299
|
+
const node = rules([
|
|
1300
|
+
style({ path: quoted(any('inline-source.css')) }, {
|
|
1301
|
+
type: 'import',
|
|
1302
|
+
importOptions: {
|
|
1303
|
+
inline: true,
|
|
1304
|
+
postlude: any('(min-width: 600px)')
|
|
1305
|
+
}
|
|
1306
|
+
})
|
|
1307
|
+
]);
|
|
1308
|
+
const evald = await node.eval(inlineContext);
|
|
1309
|
+
expect(evald.toString({ context: inlineContext })).toContain('@media (min-width: 600px)');
|
|
1310
|
+
expect(evald.toString({ context: inlineContext })).toContain('#css { color: yellow; }');
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
it('import-inline: supports/layer postludes wrap inline source in order', async () => {
|
|
1314
|
+
const inlinePath = resolve(process.cwd(), 'inline-postlude.css');
|
|
1315
|
+
const inlineContext = new Context({}, [{
|
|
1316
|
+
name: 'inline-plugin',
|
|
1317
|
+
supportedExtensions: ['.css'],
|
|
1318
|
+
resolve(filePath: string | string[]) {
|
|
1319
|
+
const paths = Array.isArray(filePath) ? filePath : [filePath];
|
|
1320
|
+
return paths.map(p => (p.endsWith('.css') ? p : `${p}.css`));
|
|
1321
|
+
},
|
|
1322
|
+
locate(pathCandidates: string[], currentDir: string) {
|
|
1323
|
+
for (const candidate of pathCandidates) {
|
|
1324
|
+
const abs = candidate.startsWith('/') ? candidate : resolve(currentDir, candidate);
|
|
1325
|
+
if (abs === inlinePath) {
|
|
1326
|
+
return abs;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return null;
|
|
1330
|
+
},
|
|
1331
|
+
async getSource() {
|
|
1332
|
+
return '#css { color: yellow; }\n';
|
|
1333
|
+
}
|
|
1334
|
+
}]);
|
|
1335
|
+
inlineContext.treeContext = {
|
|
1336
|
+
file: { name: 'entry.less', path: process.cwd(), fullPath: resolve(process.cwd(), 'entry.less') }
|
|
1337
|
+
} as any;
|
|
1338
|
+
|
|
1339
|
+
const postlude = list([
|
|
1340
|
+
call({ name: 'layer', args: list([any('theme')]) }),
|
|
1341
|
+
call({ name: 'supports', args: list([any('(display: grid)')]) }),
|
|
1342
|
+
any('screen and (min-width: 600px)')
|
|
1343
|
+
], { sep: ' ' as any });
|
|
1344
|
+
|
|
1345
|
+
const node = rules([
|
|
1346
|
+
style({ path: quoted(any('inline-postlude.css')) }, {
|
|
1347
|
+
type: 'import',
|
|
1348
|
+
importOptions: {
|
|
1349
|
+
inline: true,
|
|
1350
|
+
postlude
|
|
1351
|
+
}
|
|
1352
|
+
})
|
|
1353
|
+
]);
|
|
1354
|
+
|
|
1355
|
+
const css = (await node.eval(inlineContext)).toString({ context: inlineContext });
|
|
1356
|
+
expect(css).toContain('@layer theme');
|
|
1357
|
+
expect(css).toContain('@supports (display: grid)');
|
|
1358
|
+
expect(css).toContain('@media screen and (min-width: 600px)');
|
|
1359
|
+
expect(css).toContain('#css { color: yellow; }');
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
it('evaluated postlude wrapping does not corrupt canonical postlude parent pointers', async () => {
|
|
1363
|
+
const importPath = resolve(process.cwd(), 'postlude-parent.jess');
|
|
1364
|
+
context.sourceTrees.set(importPath, rules([
|
|
1365
|
+
ruleset({
|
|
1366
|
+
selector: sellist([sel([el('.postlude-parent')])]),
|
|
1367
|
+
rules: rules([
|
|
1368
|
+
decl({ name: any('color'), value: any('red') })
|
|
1369
|
+
])
|
|
1370
|
+
})
|
|
1371
|
+
]));
|
|
1372
|
+
|
|
1373
|
+
const layerArg = any('theme');
|
|
1374
|
+
const layerArgs = list([layerArg]);
|
|
1375
|
+
const supportsArgs = list([any('(display: grid)'), any('and (hover: hover)')]);
|
|
1376
|
+
const mediaQuery = any('screen and (min-width: 600px)');
|
|
1377
|
+
const postlude = list([
|
|
1378
|
+
call({ name: 'layer', args: layerArgs }),
|
|
1379
|
+
call({ name: 'supports', args: supportsArgs }),
|
|
1380
|
+
mediaQuery
|
|
1381
|
+
], { sep: ' ' as any });
|
|
1382
|
+
|
|
1383
|
+
expect(layerArg.parent).toBe(layerArgs);
|
|
1384
|
+
expect(supportsArgs.parent).toBe(postlude.get('value')[1]);
|
|
1385
|
+
expect(mediaQuery.parent).toBe(postlude);
|
|
1386
|
+
|
|
1387
|
+
await rules([
|
|
1388
|
+
style({ path: quoted(any('postlude-parent.jess')) }, {
|
|
1389
|
+
type: 'import',
|
|
1390
|
+
importOptions: { postlude }
|
|
1391
|
+
})
|
|
1392
|
+
]).eval(context);
|
|
1393
|
+
|
|
1394
|
+
expect(layerArg.parent).toBe(layerArgs);
|
|
1395
|
+
expect(supportsArgs.parent).toBe(postlude.get('value')[1]);
|
|
1396
|
+
expect(mediaQuery.parent).toBe(postlude);
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
it('import-interpolation: resolves vars from later imports on retry', async () => {
|
|
1400
|
+
const interpolationImportPath = resolve(process.cwd(), 'import/import-interpolation.jess');
|
|
1401
|
+
const interpolationVarsPath = resolve(process.cwd(), 'import/interpolation-vars.jess');
|
|
1402
|
+
|
|
1403
|
+
context.sourceTrees.set(interpolationImportPath, rules([
|
|
1404
|
+
vardecl({ name: 'interpolationResolved', value: any('ok') })
|
|
1405
|
+
]));
|
|
1406
|
+
context.sourceTrees.set(interpolationVarsPath, rules([
|
|
1407
|
+
vardecl({ name: 'segmentA', value: any('in') }),
|
|
1408
|
+
vardecl({ name: 'segmentB', value: any('terpolation') })
|
|
1409
|
+
]));
|
|
1410
|
+
|
|
1411
|
+
const interpolatedPath = new Interpolated({
|
|
1412
|
+
source: `import/import-${INTERPOLATION_PLACEHOLDER}${INTERPOLATION_PLACEHOLDER}.jess`,
|
|
1413
|
+
replacements: [ref('segmentA', { type: 'variable' }), ref('segmentB', { type: 'variable' })]
|
|
1414
|
+
}, { role: 'ident' });
|
|
1415
|
+
|
|
1416
|
+
const node = rules([
|
|
1417
|
+
style({ path: quoted(interpolatedPath) }, { type: 'import', importOptions: { optional: false } }),
|
|
1418
|
+
style({ path: quoted(any('import/interpolation-vars.jess')) }, { type: 'import' })
|
|
1419
|
+
]);
|
|
1420
|
+
|
|
1421
|
+
const evald = await node.eval(context);
|
|
1422
|
+
const resolvedFromInterpolatedImport = getVarWithContext(context, evald, 'interpolationResolved');
|
|
1423
|
+
expect(resolvedFromInterpolatedImport).toBeDefined();
|
|
1424
|
+
expect(resolvedFromInterpolatedImport.toTrimmedString({ context })).toBe('$interpolationResolved: ok');
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
it('import path resolution uses a state-evaluated Url path value', async () => {
|
|
1428
|
+
const resolvedImportPath = resolve(process.cwd(), 'import/url-state-path.jess');
|
|
1429
|
+
context.sourceTrees.set(resolvedImportPath, rules([
|
|
1430
|
+
vardecl({ name: 'resolvedFromUrl', value: any('ok') })
|
|
1431
|
+
]));
|
|
1432
|
+
|
|
1433
|
+
const importNode = style({
|
|
1434
|
+
path: url(quoted('wrong-path.jess'))
|
|
1435
|
+
}, { type: 'import' });
|
|
1436
|
+
const renderKey = context.nextRenderKey();
|
|
1437
|
+
addEdge(importNode, 'path', renderKey, url(quoted('import/url-state-path.jess')));
|
|
1438
|
+
importNode.renderKey = renderKey;
|
|
1439
|
+
const node = rules([
|
|
1440
|
+
importNode
|
|
1441
|
+
]);
|
|
1442
|
+
const preEvald = await (node as any).preEval(context);
|
|
1443
|
+
const preEvaldImport = preEvald.at(0, context);
|
|
1444
|
+
|
|
1445
|
+
const evald = await node.eval(context);
|
|
1446
|
+
const resolvedFromUrl = getVarWithContext(context, evald, 'resolvedFromUrl');
|
|
1447
|
+
|
|
1448
|
+
expect(resolvedFromUrl).toBeDefined();
|
|
1449
|
+
expect(resolvedFromUrl.toTrimmedString({ context })).toBe('$resolvedFromUrl: ok');
|
|
1450
|
+
});
|
|
1451
|
+
|
|
1452
|
+
it('import-module: context can resolve bare module-like specifiers', async () => {
|
|
1453
|
+
const moduleContext = new Context();
|
|
1454
|
+
moduleContext.treeContext = {
|
|
1455
|
+
file: { name: 'entry.less', path: process.cwd(), fullPath: resolve(process.cwd(), 'entry.less') }
|
|
1456
|
+
} as any;
|
|
1457
|
+
const result = await (moduleContext as any)._getPath('lodash-es');
|
|
1458
|
+
expect(typeof result.resolvedPath).toBe('string');
|
|
1459
|
+
expect(result.resolvedPath.length).toBeGreaterThan(0);
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
it('import-once: default once semantics de-dupe repeated imports', async () => {
|
|
1463
|
+
const oncePath = resolve(process.cwd(), 'once.jess');
|
|
1464
|
+
context.sourceTrees.set(oncePath, rules([
|
|
1465
|
+
ruleset({
|
|
1466
|
+
selector: sellist([sel([el('.once')])]),
|
|
1467
|
+
rules: rules([decl({ name: any('color'), value: any('red') })])
|
|
1468
|
+
})
|
|
1469
|
+
]));
|
|
1470
|
+
|
|
1471
|
+
const node = rules([
|
|
1472
|
+
style({ path: quoted(any('once.jess')) }, { type: 'import' }),
|
|
1473
|
+
style({ path: quoted(any('once.jess')) }, { type: 'import' })
|
|
1474
|
+
]);
|
|
1475
|
+
const evald = await node.eval(context);
|
|
1476
|
+
expect(evald.render(context).split('.once').length - 1).toBe(1);
|
|
1477
|
+
});
|
|
1478
|
+
|
|
1479
|
+
it('second same-file import defaults to reference mode without multiple', async () => {
|
|
1480
|
+
const libraryPath = resolve(process.cwd(), 'import-reference-default.jess');
|
|
1481
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
1482
|
+
ruleset({
|
|
1483
|
+
selector: sellist([sel([el('.once-ref')])]),
|
|
1484
|
+
rules: rules([decl({ name: any('color'), value: any('red') })])
|
|
1485
|
+
})
|
|
1486
|
+
]));
|
|
1487
|
+
|
|
1488
|
+
const node = rules([
|
|
1489
|
+
style({ path: quoted(any('import-reference-default.jess')) }, { type: 'import' }),
|
|
1490
|
+
style({ path: quoted(any('import-reference-default.jess')) }, { type: 'import' })
|
|
1491
|
+
]);
|
|
1492
|
+
|
|
1493
|
+
const evald = await node.eval(context);
|
|
1494
|
+
const first = evald.at(0, context) as Rules;
|
|
1495
|
+
const second = evald.at(1, context) as Rules;
|
|
1496
|
+
|
|
1497
|
+
expect(first.options.referenceMode).not.toBe(true);
|
|
1498
|
+
expect(second.options.referenceMode).toBe(true);
|
|
1499
|
+
expect(evald.render(context).split('.once-ref').length - 1).toBe(1);
|
|
1500
|
+
});
|
|
1501
|
+
|
|
1502
|
+
it('import-reference-issues: reference imports are optional visibility', async () => {
|
|
1503
|
+
const referencedPath = resolve(process.cwd(), 'reference-issues.jess');
|
|
1504
|
+
context.sourceTrees.set(referencedPath, rules([
|
|
1505
|
+
ruleset({
|
|
1506
|
+
selector: sellist([sel([el('.hidden')])]),
|
|
1507
|
+
rules: rules([decl({ name: any('color'), value: any('red') })])
|
|
1508
|
+
})
|
|
1509
|
+
]));
|
|
1510
|
+
const node = rules([
|
|
1511
|
+
style({ path: quoted(any('reference-issues.jess')) }, { type: 'import', importOptions: { reference: true } })
|
|
1512
|
+
]);
|
|
1513
|
+
const evald = await node.eval(context);
|
|
1514
|
+
const imported = evald.at(0, context) as Rules;
|
|
1515
|
+
expect(imported.options.rulesVisibility?.Ruleset).toBe('optional');
|
|
1516
|
+
});
|
|
1517
|
+
|
|
1518
|
+
it('import-reference: reference imports remain discoverable for lookups', async () => {
|
|
1519
|
+
const referencedPath = resolve(process.cwd(), 'reference.jess');
|
|
1520
|
+
context.sourceTrees.set(referencedPath, rules([
|
|
1521
|
+
vardecl({ name: 'fromRef', value: any('42') })
|
|
1522
|
+
]));
|
|
1523
|
+
const node = rules([
|
|
1524
|
+
style({ path: quoted(any('reference.jess')) }, { type: 'import', importOptions: { reference: true } }),
|
|
1525
|
+
decl({ name: any('value'), value: ref('fromRef', { type: 'variable' }) })
|
|
1526
|
+
]);
|
|
1527
|
+
const evald = await node.eval(context);
|
|
1528
|
+
const declaration = evald.at(1, context) as any;
|
|
1529
|
+
const evaldCtx = asRulesContext(context, evald);
|
|
1530
|
+
const resolved = await declaration.eval(evaldCtx);
|
|
1531
|
+
expect(resolved.toTrimmedString({ context: evaldCtx })).toBe('value: 42');
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
it('reference import call output rebinds nested rulesets to the caller instead of the definition', async () => {
|
|
1535
|
+
const referencedPath = resolve(process.cwd(), 'reference-call-shape.jess');
|
|
1536
|
+
context.sourceTrees.set(referencedPath, rules([
|
|
1537
|
+
ruleset({
|
|
1538
|
+
selector: sellist([sel([el('.z')])]),
|
|
1539
|
+
rules: rules([
|
|
1540
|
+
decl({ name: any('color'), value: any('red') }),
|
|
1541
|
+
ruleset({
|
|
1542
|
+
selector: sellist([sel([el('.c')])]),
|
|
1543
|
+
rules: rules([decl({ name: any('color'), value: any('green') })])
|
|
1544
|
+
})
|
|
1545
|
+
])
|
|
1546
|
+
}),
|
|
1547
|
+
ruleset({
|
|
1548
|
+
selector: sellist([sel([el('.only-with-visible')]), sel([el('.z')])]),
|
|
1549
|
+
rules: rules([
|
|
1550
|
+
decl({ name: any('color'), value: any('green') }),
|
|
1551
|
+
ruleset({
|
|
1552
|
+
selector: sellist([compound([amp(), pseudo({ name: ':hover' })])]),
|
|
1553
|
+
rules: rules([decl({ name: any('color'), value: any('green') })])
|
|
1554
|
+
}),
|
|
1555
|
+
ruleset({
|
|
1556
|
+
selector: sellist([sel([amp()])]),
|
|
1557
|
+
rules: rules([decl({ name: any('color'), value: any('green') })])
|
|
1558
|
+
}),
|
|
1559
|
+
ruleset({
|
|
1560
|
+
selector: sellist([sel([amp(), co('+'), amp()])]),
|
|
1561
|
+
rules: rules([
|
|
1562
|
+
decl({ name: any('color'), value: any('green') }),
|
|
1563
|
+
ruleset({
|
|
1564
|
+
selector: sellist([sel([el('.sub')])]),
|
|
1565
|
+
rules: rules([decl({ name: any('color'), value: any('green') })])
|
|
1566
|
+
})
|
|
1567
|
+
])
|
|
1568
|
+
})
|
|
1569
|
+
])
|
|
1570
|
+
}),
|
|
1571
|
+
ruleset({
|
|
1572
|
+
selector: sellist([sel([el('.zz')])]),
|
|
1573
|
+
rules: rules([
|
|
1574
|
+
ruleset({
|
|
1575
|
+
selector: sellist([sel([el('.y')])]),
|
|
1576
|
+
rules: rules([
|
|
1577
|
+
decl({ name: any('pulled-in'), value: any('yes') })
|
|
1578
|
+
])
|
|
1579
|
+
})
|
|
1580
|
+
])
|
|
1581
|
+
})
|
|
1582
|
+
]));
|
|
1583
|
+
|
|
1584
|
+
const node = rules([
|
|
1585
|
+
style({ path: quoted(any('reference-call-shape.jess')) }, {
|
|
1586
|
+
type: 'import',
|
|
1587
|
+
importOptions: { reference: true }
|
|
1588
|
+
}),
|
|
1589
|
+
ruleset({
|
|
1590
|
+
selector: sellist([sel([el('.b')])]),
|
|
1591
|
+
rules: rules([
|
|
1592
|
+
call({ name: ref({ key: '.z' }, { type: 'mixin-ruleset' }) })
|
|
1593
|
+
])
|
|
1594
|
+
}),
|
|
1595
|
+
call({ name: ref({ key: '.zz' }, { type: 'mixin-ruleset' }) })
|
|
1596
|
+
]);
|
|
1597
|
+
|
|
1598
|
+
context.opts.collapseNesting = true;
|
|
1599
|
+
|
|
1600
|
+
const evald = await node.eval(context);
|
|
1601
|
+
const css = evald.toString({ context });
|
|
1602
|
+
|
|
1603
|
+
expect(css).toBeString(`
|
|
1604
|
+
.b {
|
|
1605
|
+
color: red;
|
|
1606
|
+
}
|
|
1607
|
+
.b .c {
|
|
1608
|
+
color: green;
|
|
1609
|
+
}
|
|
1610
|
+
.b {
|
|
1611
|
+
color: green;
|
|
1612
|
+
}
|
|
1613
|
+
.b:hover {
|
|
1614
|
+
color: green;
|
|
1615
|
+
}
|
|
1616
|
+
.b {
|
|
1617
|
+
color: green;
|
|
1618
|
+
}
|
|
1619
|
+
.b + .b {
|
|
1620
|
+
color: green;
|
|
1621
|
+
}
|
|
1622
|
+
.b + .b .sub {
|
|
1623
|
+
color: green;
|
|
1624
|
+
}
|
|
1625
|
+
.y {
|
|
1626
|
+
pulled-in: yes;
|
|
1627
|
+
}
|
|
1628
|
+
`);
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
it('import-remote: mapped remote package paths can be resolved as module-like imports', async () => {
|
|
1632
|
+
const remoteContext = new Context({}, [{
|
|
1633
|
+
name: 'remote-map',
|
|
1634
|
+
supportedExtensions: ['.less'],
|
|
1635
|
+
resolve(filePath: string | string[], currentDir: string, searchPaths: string[]) {
|
|
1636
|
+
const paths = Array.isArray(filePath) ? filePath : [filePath];
|
|
1637
|
+
const mapped = paths.map((candidate) => {
|
|
1638
|
+
const m = candidate.match(/^https?:\/\/cdn\.jsdelivr\.net\/npm\/([^?#]+)(?:[?#].*)?$/i);
|
|
1639
|
+
return m?.[1] ?? candidate;
|
|
1640
|
+
});
|
|
1641
|
+
void currentDir;
|
|
1642
|
+
void searchPaths;
|
|
1643
|
+
return mapped;
|
|
1644
|
+
},
|
|
1645
|
+
locate() {
|
|
1646
|
+
return null;
|
|
1647
|
+
}
|
|
1648
|
+
}]);
|
|
1649
|
+
remoteContext.treeContext = {
|
|
1650
|
+
file: { name: 'entry.less', path: process.cwd(), fullPath: resolve(process.cwd(), 'entry.less') }
|
|
1651
|
+
} as any;
|
|
1652
|
+
const result = await (remoteContext as any)._getPath('https://cdn.jsdelivr.net/npm/lodash-es/lodash.js');
|
|
1653
|
+
expect(typeof result.resolvedPath).toBe('string');
|
|
1654
|
+
expect(result.resolvedPath.length).toBeGreaterThan(0);
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
it('import.less: optional missing imports do not throw and produce empty rules', async () => {
|
|
1658
|
+
const node = rules([
|
|
1659
|
+
style({ path: quoted(any('missing-file.jess')) }, { type: 'import', importOptions: { optional: true } })
|
|
1660
|
+
]);
|
|
1661
|
+
const evald = await node.eval(context);
|
|
1662
|
+
expect(evald.value.length).toBe(1);
|
|
1663
|
+
const imported = evald.at(0, context) as Rules;
|
|
1664
|
+
expect(imported.value.length).toBe(0);
|
|
1665
|
+
});
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
describe('reference/multiple dedupe matrix', () => {
|
|
1669
|
+
const countSelector = (css: string, selector: string) => css.split(selector).length - 1;
|
|
1670
|
+
|
|
1671
|
+
it('import once:false renders repeated imports', async () => {
|
|
1672
|
+
context.sourceTrees.set('repeat.jess', rules([
|
|
1673
|
+
ruleset({
|
|
1674
|
+
selector: sellist([sel([el('.repeat')])]),
|
|
1675
|
+
rules: rules([decl({ name: any('color'), value: any('red') })])
|
|
1676
|
+
})
|
|
1677
|
+
]));
|
|
1678
|
+
const node = rules([
|
|
1679
|
+
style({ path: quoted(any('repeat.jess')) }, { type: 'import', importOptions: { once: false } }),
|
|
1680
|
+
style({ path: quoted(any('repeat.jess')) }, { type: 'import', importOptions: { once: false } })
|
|
1681
|
+
]);
|
|
1682
|
+
const evald = await node.eval(context);
|
|
1683
|
+
expect(countSelector(evald.render(context), '.repeat')).toBe(2);
|
|
1684
|
+
});
|
|
1685
|
+
|
|
1686
|
+
it('plain import followed by reference import renders once', async () => {
|
|
1687
|
+
context.sourceTrees.set('mix-order.jess', rules([
|
|
1688
|
+
ruleset({
|
|
1689
|
+
selector: sellist([sel([el('.mix-order')])]),
|
|
1690
|
+
rules: rules([decl({ name: any('color'), value: any('red') })])
|
|
1691
|
+
})
|
|
1692
|
+
]));
|
|
1693
|
+
const node = rules([
|
|
1694
|
+
style({ path: quoted(any('mix-order.jess')) }, { type: 'import' }),
|
|
1695
|
+
style(
|
|
1696
|
+
{ path: quoted(any('mix-order.jess')) },
|
|
1697
|
+
{ type: 'import', importOptions: { reference: true } }
|
|
1698
|
+
)
|
|
1699
|
+
]);
|
|
1700
|
+
const evald = await node.eval(context);
|
|
1701
|
+
expect(countSelector(evald.render(context), '.mix-order')).toBe(1);
|
|
1702
|
+
});
|
|
1703
|
+
|
|
1704
|
+
it('reference import followed by plain import stays suppressed without multiple', async () => {
|
|
1705
|
+
context.sourceTrees.set('mix-order-rev.jess', rules([
|
|
1706
|
+
ruleset({
|
|
1707
|
+
selector: sellist([sel([el('.mix-order-rev')])]),
|
|
1708
|
+
rules: rules([decl({ name: any('color'), value: any('red') })])
|
|
1709
|
+
})
|
|
1710
|
+
]));
|
|
1711
|
+
const node = rules([
|
|
1712
|
+
style(
|
|
1713
|
+
{ path: quoted(any('mix-order-rev.jess')) },
|
|
1714
|
+
{ type: 'import', importOptions: { reference: true } }
|
|
1715
|
+
),
|
|
1716
|
+
style({ path: quoted(any('mix-order-rev.jess')) }, { type: 'import' })
|
|
1717
|
+
]);
|
|
1718
|
+
const evald = await node.eval(context);
|
|
1719
|
+
expect(countSelector(evald.render(context), '.mix-order-rev')).toBe(0);
|
|
1720
|
+
});
|
|
1721
|
+
|
|
1722
|
+
it('deduped imports do not corrupt canonical top-level ruleset child parents', async () => {
|
|
1723
|
+
const importedRuleset = ruleset({
|
|
1724
|
+
selector: sellist([sel([el('.dedupe-parent')])]),
|
|
1725
|
+
rules: rules([decl({ name: any('color'), value: any('red') })])
|
|
1726
|
+
});
|
|
1727
|
+
const sourceRules = rules([importedRuleset]);
|
|
1728
|
+
context.sourceTrees.set('dedupe-parents.jess', sourceRules);
|
|
1729
|
+
|
|
1730
|
+
expect(importedRuleset.get('rules').parent).toBe(importedRuleset);
|
|
1731
|
+
expect(importedRuleset.get('selector').parent).toBe(importedRuleset);
|
|
1732
|
+
|
|
1733
|
+
const node = rules([
|
|
1734
|
+
style({ path: quoted(any('dedupe-parents.jess')) }, { type: 'import' }),
|
|
1735
|
+
style({ path: quoted(any('dedupe-parents.jess')) }, { type: 'import' })
|
|
1736
|
+
]);
|
|
1737
|
+
|
|
1738
|
+
await node.eval(context);
|
|
1739
|
+
|
|
1740
|
+
expect(importedRuleset.get('rules').parent).toBe(importedRuleset);
|
|
1741
|
+
expect(importedRuleset.get('selector').parent).toBe(importedRuleset);
|
|
1742
|
+
});
|
|
1743
|
+
|
|
1744
|
+
it('deduped imports materialize top-level declaration parents in returned trees without corrupting canonical source parents', async () => {
|
|
1745
|
+
const libraryPath = resolve(process.cwd(), 'dedupe-vars.jess');
|
|
1746
|
+
const importedVar = vardecl({ name: 'dedupeVar', value: any('red') });
|
|
1747
|
+
const sourceRules = rules([importedVar]);
|
|
1748
|
+
context.sourceTrees.set(libraryPath, sourceRules);
|
|
1749
|
+
const cachedEvaldRules = rules([
|
|
1750
|
+
vardecl({ name: 'dedupeVar', value: any('red') })
|
|
1751
|
+
]);
|
|
1752
|
+
context.evaldTrees.set(libraryPath, cachedEvaldRules);
|
|
1753
|
+
|
|
1754
|
+
expect(importedVar.parent).toBe(sourceRules);
|
|
1755
|
+
|
|
1756
|
+
const node = rules([
|
|
1757
|
+
style({ path: quoted(any('dedupe-vars.jess')) }, { type: 'import' })
|
|
1758
|
+
]);
|
|
1759
|
+
|
|
1760
|
+
const evald = await node.eval(context);
|
|
1761
|
+
const dedupedImport = evald.at(0, context) as Rules;
|
|
1762
|
+
const dedupedVar = dedupedImport.at(0, context) as VarDeclaration;
|
|
1763
|
+
const dedupedCtx = asRulesContext(context, dedupedImport);
|
|
1764
|
+
|
|
1765
|
+
expect(getParent(dedupedVar, dedupedCtx)).toBe(dedupedImport);
|
|
1766
|
+
expect(dedupedVar).not.toBe(importedVar);
|
|
1767
|
+
expect(dedupedVar.toTrimmedString({ context })).toBe('$dedupeVar: red');
|
|
1768
|
+
expect(importedVar.parent).toBe(sourceRules);
|
|
1769
|
+
});
|
|
1770
|
+
|
|
1771
|
+
it('shallow top-level child clones keep nested canonical children parented to the source ruleset', () => {
|
|
1772
|
+
const canonicalRuleset = ruleset({
|
|
1773
|
+
selector: sellist([sel([el('.dedupe-shallow')])]),
|
|
1774
|
+
rules: rules([decl({ name: any('color'), value: any('red') })])
|
|
1775
|
+
});
|
|
1776
|
+
|
|
1777
|
+
const canonicalSelector = canonicalRuleset.get('selector');
|
|
1778
|
+
const canonicalBody = canonicalRuleset.get('rules');
|
|
1779
|
+
|
|
1780
|
+
expect(canonicalSelector.parent).toBe(canonicalRuleset);
|
|
1781
|
+
expect(canonicalBody.parent).toBe(canonicalRuleset);
|
|
1782
|
+
|
|
1783
|
+
const shallowClone = canonicalRuleset.clone(false);
|
|
1784
|
+
|
|
1785
|
+
expect(shallowClone).not.toBe(canonicalRuleset);
|
|
1786
|
+
expect(shallowClone.get('selector')).toBe(canonicalSelector);
|
|
1787
|
+
expect(shallowClone.get('rules')).toBe(canonicalBody);
|
|
1788
|
+
expect(canonicalSelector.parent).toBe(canonicalRuleset);
|
|
1789
|
+
expect(canonicalBody.parent).toBe(canonicalRuleset);
|
|
1790
|
+
expect(shallowClone.toTrimmedString({ context })).toContain('color: red');
|
|
1791
|
+
});
|
|
1792
|
+
|
|
1793
|
+
it('returned-tree wrappers do not canonically reparent shared top-level children', () => {
|
|
1794
|
+
const canonicalRuleset = ruleset({
|
|
1795
|
+
selector: sellist([sel([el('.wrapper-blocker')])]),
|
|
1796
|
+
rules: rules([decl({ name: any('color'), value: any('red') })])
|
|
1797
|
+
});
|
|
1798
|
+
const evaluatedRules = rules([canonicalRuleset]);
|
|
1799
|
+
|
|
1800
|
+
expect(canonicalRuleset.parent).toBe(evaluatedRules);
|
|
1801
|
+
|
|
1802
|
+
const shallowWrapper = evaluatedRules.clone(false);
|
|
1803
|
+
|
|
1804
|
+
expect(shallowWrapper).not.toBe(evaluatedRules);
|
|
1805
|
+
expect(shallowWrapper.at(0, context)).toBe(canonicalRuleset);
|
|
1806
|
+
expect(canonicalRuleset.parent).toBe(evaluatedRules);
|
|
1807
|
+
expect(shallowWrapper.toTrimmedString({ context })).toContain('.wrapper-blocker');
|
|
1808
|
+
});
|
|
1809
|
+
|
|
1810
|
+
it.skip('deduped import wrappers keep cached evaluated parent pointers stable with detached wrapper finalization', async () => {
|
|
1811
|
+
const libraryPath = resolve(process.cwd(), 'dedupe-ruleset-identity.jess');
|
|
1812
|
+
const sourceRuleset = ruleset({
|
|
1813
|
+
selector: sellist([sel([el('.dedupe-identity')])]),
|
|
1814
|
+
rules: rules([
|
|
1815
|
+
decl({ name: any('color'), value: ref('libColor', { type: 'variable' }) })
|
|
1816
|
+
])
|
|
1817
|
+
});
|
|
1818
|
+
context.sourceTrees.set(libraryPath, rules([sourceRuleset]));
|
|
1819
|
+
|
|
1820
|
+
const cachedRuleset = ruleset({
|
|
1821
|
+
selector: sellist([sel([el('.dedupe-identity')])]),
|
|
1822
|
+
rules: rules([
|
|
1823
|
+
decl({ name: any('color'), value: any('red') })
|
|
1824
|
+
])
|
|
1825
|
+
});
|
|
1826
|
+
cachedRuleset.sourceNode = sourceRuleset;
|
|
1827
|
+
const cachedEvaldRules = rules([cachedRuleset]);
|
|
1828
|
+
cachedEvaldRules.sourceNode = context.sourceTrees.get(libraryPath)!;
|
|
1829
|
+
context.evaldTrees.set(libraryPath, cachedEvaldRules);
|
|
1830
|
+
const originalCachedParent = cachedRuleset.parent;
|
|
1831
|
+
|
|
1832
|
+
const node = rules([
|
|
1833
|
+
style({ path: quoted(any('dedupe-ruleset-identity.jess')) }, { type: 'import' })
|
|
1834
|
+
]);
|
|
1835
|
+
|
|
1836
|
+
const evald = await node.eval(context);
|
|
1837
|
+
const dedupedImport = evald.at(0, context) as Rules;
|
|
1838
|
+
const dedupedRuleset = dedupedImport.at(0, context) as Ruleset;
|
|
1839
|
+
|
|
1840
|
+
expect(dedupedRuleset).not.toBe(cachedRuleset);
|
|
1841
|
+
expect(dedupedRuleset.parent).toBe(dedupedImport);
|
|
1842
|
+
expect(cachedRuleset.parent).toBe(originalCachedParent);
|
|
1843
|
+
expect(dedupedRuleset.toTrimmedString({ context })).toContain('color: red');
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
it('compose multiple:true renders repeated modules', async () => {
|
|
1847
|
+
context.sourceTrees.set('compose-repeat.jess', rules([
|
|
1848
|
+
ruleset({
|
|
1849
|
+
selector: sellist([sel([el('.compose-repeat')])]),
|
|
1850
|
+
rules: rules([decl({ name: any('color'), value: any('red') })])
|
|
1851
|
+
})
|
|
1852
|
+
]));
|
|
1853
|
+
const node = rules([
|
|
1854
|
+
style(
|
|
1855
|
+
{ path: quoted(any('compose-repeat.jess')) },
|
|
1856
|
+
{ type: 'compose', namespace: '*', importOptions: { multiple: true } }
|
|
1857
|
+
),
|
|
1858
|
+
style(
|
|
1859
|
+
{ path: quoted(any('compose-repeat.jess')) },
|
|
1860
|
+
{ type: 'compose', namespace: '*', importOptions: { multiple: true } }
|
|
1861
|
+
)
|
|
1862
|
+
]);
|
|
1863
|
+
const evald = await node.eval(context);
|
|
1864
|
+
expect(countSelector(evald.render(context), '.compose-repeat')).toBe(2);
|
|
1865
|
+
});
|
|
1866
|
+
});
|
|
1867
|
+
|
|
1868
|
+
describe('compose caching', () => {
|
|
1869
|
+
it('caches evaluated compose modules and de-dupes output unless multiple=true', async () => {
|
|
1870
|
+
context.sourceTrees.set('library-dedupe.jess', rules([
|
|
1871
|
+
ruleset({
|
|
1872
|
+
selector: sellist([sel([el('.imported')])]),
|
|
1873
|
+
rules: rules([
|
|
1874
|
+
decl({ name: any('color'), value: any('red') })
|
|
1875
|
+
])
|
|
1876
|
+
})
|
|
1877
|
+
]));
|
|
1878
|
+
|
|
1879
|
+
const node = rules([
|
|
1880
|
+
style({ path: quoted(any('library-dedupe.jess')) }, { type: 'compose', namespace: '*' }),
|
|
1881
|
+
style({ path: quoted(any('library-dedupe.jess')) }, { type: 'compose', namespace: '*' })
|
|
1882
|
+
]);
|
|
1883
|
+
|
|
1884
|
+
const evald = await node.eval(context);
|
|
1885
|
+
const css = evald.render(context);
|
|
1886
|
+
// Should only render `.imported` once (second compose is reference mode by default).
|
|
1887
|
+
expect(css.split('.imported').length - 1).toBe(1);
|
|
1888
|
+
});
|
|
1889
|
+
|
|
1890
|
+
it('still allows per-import visibility differences via shallow clone', async () => {
|
|
1891
|
+
context.sourceTrees.set('library-vis.jess', rules([
|
|
1892
|
+
ruleset({
|
|
1893
|
+
selector: sellist([sel([el('.imported')])]),
|
|
1894
|
+
rules: rules([
|
|
1895
|
+
decl({ name: any('color'), value: any('red') })
|
|
1896
|
+
])
|
|
1897
|
+
})
|
|
1898
|
+
]));
|
|
1899
|
+
|
|
1900
|
+
const node = rules([
|
|
1901
|
+
style(
|
|
1902
|
+
{ path: quoted(any('library-vis.jess')) },
|
|
1903
|
+
{ type: 'compose', namespace: '*', importOptions: { mutable: true } }
|
|
1904
|
+
),
|
|
1905
|
+
style(
|
|
1906
|
+
{ path: quoted(any('library-vis.jess')) },
|
|
1907
|
+
{ type: 'compose', namespace: '*', importOptions: { mutable: false, multiple: true } }
|
|
1908
|
+
)
|
|
1909
|
+
]);
|
|
1910
|
+
|
|
1911
|
+
const evald = await node.eval(context);
|
|
1912
|
+
expect(evald.value.length).toBe(2);
|
|
1913
|
+
const first = evald.at(0, context) as Rules;
|
|
1914
|
+
const second = evald.at(1, context) as Rules;
|
|
1915
|
+
expect(first.options.rulesVisibility.Ruleset).toBe('public');
|
|
1916
|
+
expect(second.options.rulesVisibility.Ruleset).toBe('private');
|
|
1917
|
+
});
|
|
1918
|
+
|
|
1919
|
+
it.skip('compose cache wrappers still share top-level evaluated child identity across per-import visibility wrappers', async () => {
|
|
1920
|
+
const libraryPath = 'library-wrapper-contract.jess';
|
|
1921
|
+
context.sourceTrees.set(libraryPath, rules([
|
|
1922
|
+
ruleset({
|
|
1923
|
+
selector: sellist([sel([el('.imported')])]),
|
|
1924
|
+
rules: rules([
|
|
1925
|
+
decl({ name: any('color'), value: any('red') })
|
|
1926
|
+
])
|
|
1927
|
+
})
|
|
1928
|
+
]));
|
|
1929
|
+
|
|
1930
|
+
const node = rules([
|
|
1931
|
+
style(
|
|
1932
|
+
{ path: quoted(any(libraryPath)) },
|
|
1933
|
+
{ type: 'compose', namespace: '*', importOptions: { mutable: true } }
|
|
1934
|
+
),
|
|
1935
|
+
style(
|
|
1936
|
+
{ path: quoted(any(libraryPath)) },
|
|
1937
|
+
{ type: 'compose', namespace: '*', importOptions: { mutable: false, multiple: true } }
|
|
1938
|
+
)
|
|
1939
|
+
]);
|
|
1940
|
+
|
|
1941
|
+
const evald = await node.eval(context);
|
|
1942
|
+
const first = evald.at(0, context) as Rules;
|
|
1943
|
+
const second = evald.at(1, context) as Rules;
|
|
1944
|
+
const firstRuleset = first.at(0, context) as Ruleset;
|
|
1945
|
+
const secondRuleset = second.at(0, context) as Ruleset;
|
|
1946
|
+
const cachedEvaldRules = context.evaldTrees.get(libraryPath)!;
|
|
1947
|
+
const cachedRuleset = cachedEvaldRules.at(0, context) as Ruleset;
|
|
1948
|
+
|
|
1949
|
+
expect(first.options.rulesVisibility.Ruleset).toBe('public');
|
|
1950
|
+
expect(second.options.rulesVisibility.Ruleset).toBe('private');
|
|
1951
|
+
expect(cachedEvaldRules).not.toBe(first);
|
|
1952
|
+
expect(cachedEvaldRules).not.toBe(second);
|
|
1953
|
+
expect(first.options).not.toBe(second.options);
|
|
1954
|
+
expect(first.options).not.toBe(cachedEvaldRules.options);
|
|
1955
|
+
expect(second.options).not.toBe(cachedEvaldRules.options);
|
|
1956
|
+
expect(first.value).toBe(cachedEvaldRules.value);
|
|
1957
|
+
expect(second.value).toBe(cachedEvaldRules.value);
|
|
1958
|
+
expect(first.value[0]).toBe(cachedRuleset);
|
|
1959
|
+
expect(second.value[0]).toBe(cachedRuleset);
|
|
1960
|
+
expect(firstRuleset).toBe(cachedRuleset);
|
|
1961
|
+
expect(firstRuleset).toBe(secondRuleset);
|
|
1962
|
+
expect(firstRuleset.parent).not.toBe(first);
|
|
1963
|
+
expect(firstRuleset.parent).not.toBe(second);
|
|
1964
|
+
expect(firstRuleset.toTrimmedString({ context })).toContain('.imported');
|
|
1965
|
+
});
|
|
1966
|
+
});
|
|
1967
|
+
});
|