Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/shared/declaration/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,75 @@ describe('renderDeclarations', () => {

expect(output).toContain('.dark.secondary-theme .navigation,.dark.navigation.secondary-theme');
});

test('context token that differs from base is present in the same-element context rule', () => {
// shadow is overridden in navigationContext. The same-element context rule
// (.navigation.secondary-theme) must contain it, not just the descendant form.
// If both rules have identical content they will be merged into a comma-selector.
const output = createBuildDeclarations(
secondaryTheme,
[],
preset.propertiesMap,
(selector) => selector,
Object.keys(secondaryTheme.tokens),
);

const mergedRule = output.match(/\.secondary-theme \.navigation,\.navigation\.secondary-theme\s*\{([^}]*)\}/);
expect(mergedRule).not.toBeNull();
expect(mergedRule![1]).toContain('--shadow-css');
});

test('context token that differs from base is present in the same-element context rule (multi-theme path)', () => {
// Same as above but via MultiThemeCreator (primary + secondary).
// The merged comma-selector proves both rules have identical content — if the
// same-element rule were diffed against a different parent, it would contain
// extra tokens and the rules would not merge.
// boxShadow is set in the primary's navigationContext but not in the secondary's,
// so it must be present in both the descendant and same-element secondary context rules.
const output = createBuildDeclarations(
rootTheme,
[secondaryTheme],
preset.propertiesMap,
(selector) => selector,
Object.keys(rootTheme.tokens),
);

const mergedRule = output.match(/\.secondary-theme \.navigation,\.navigation\.secondary-theme\s*\{([^}]*)\}/);
expect(mergedRule).not.toBeNull();
expect(mergedRule![1]).toContain('--boxShadow-css');
});

test('mode+context descendant and same-element rules have identical declarations and are merged', () => {
// Both selectors must resolve to the same declarations so mergeSelectors can combine them.
const output = createBuildDeclarations(
secondaryTheme,
[],
preset.propertiesMap,
(selector) => selector,
Object.keys(secondaryTheme.tokens),
);

const mergedRule = output.match(
/\.dark\.secondary-theme \.navigation,\.dark\.navigation\.secondary-theme\s*\{([^}]*)\}/,
);
expect(mergedRule).not.toBeNull();
expect(mergedRule![1]).toContain('--shadow-css');
});

test('mode+context descendant and same-element rules have identical declarations and are merged (multi-theme path)', () => {
// Same as above but via MultiThemeCreator (primary + secondary).
const output = createBuildDeclarations(
rootTheme,
[secondaryTheme],
preset.propertiesMap,
(selector) => selector,
Object.keys(rootTheme.tokens),
);

const mergedRule = output.match(
/\.dark\.secondary-theme \.navigation,\.dark\.navigation\.secondary-theme\s*\{([^}]*)\}/,
);
expect(mergedRule).not.toBeNull();
expect(mergedRule![1]).toContain('--shadow-css');
});
});
5 changes: 3 additions & 2 deletions src/shared/declaration/multi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export class MultiThemeCreator extends AbstractCreator implements StylesheetCrea
MultiThemeCreator.appendRuleToStylesheet(
stylesheet,
contextRuleGlobal,
compact([parentContextRule, rootRule, parentRule]),
compact([rootRule, parentContextRule, parentRule]),
);
});

Expand All @@ -126,6 +126,7 @@ export class MultiThemeCreator extends AbstractCreator implements StylesheetCrea
modeReducer(mode, state),
);
const contextRule = this.findRule(stylesheet, { global: [secondary.selector], local: [context.selector] });
const contextRuleGlobal = this.findRule(stylesheet, { global: [secondary.selector, context.selector] });
const modeRule = this.findRule(stylesheet, {
global: [secondary.selector, optionalState.selector],
});
Expand Down Expand Up @@ -179,7 +180,7 @@ export class MultiThemeCreator extends AbstractCreator implements StylesheetCrea
stylesheet,
contextAndModeRuleGlobal,
compact([
contextRule,
contextRuleGlobal,
parentContextAndModeRule,
parentContextRule,
modeRule,
Expand Down
5 changes: 4 additions & 1 deletion src/shared/declaration/single.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ export class SingleThemeCreator extends AbstractCreator implements StylesheetCre
const contextRule = stylesheet.findRule(
this.ruleCreator.selectorFor({ global: [this.theme.selector], local: [context.selector] }),
);
const contextRuleGlobal = stylesheet.findRule(
this.ruleCreator.selectorFor({ global: [this.theme.selector, context.selector] }),
);
const modeRule = stylesheet.findRule(
this.ruleCreator.selectorFor({
global: [this.theme.selector, (mode.states[state] as OptionalState).selector],
Expand All @@ -105,7 +108,7 @@ export class SingleThemeCreator extends AbstractCreator implements StylesheetCre
SingleThemeCreator.appendRuleToStylesheet(
stylesheet,
contextRuleAndModeRuleGlobal,
compact([contextRule, modeRule, rootRule]),
compact([contextRuleGlobal, modeRule, rootRule]),
);
});

Expand Down
Loading