diff --git a/frontend/resources/vue/App.vue b/frontend/resources/vue/App.vue index 3acbe21..27bb425 100644 --- a/frontend/resources/vue/App.vue +++ b/frontend/resources/vue/App.vue @@ -361,10 +361,12 @@ function applyTheme() { document.head.appendChild(themeStyle) } + // Load theme into @layer theme so it takes precedence over @layer components (theme is + // last in style.css layer order). Fixes #804 regression after beta.2. if (themePreference.value && themePreference.value !== '') { - themeStyle.textContent = `@import url('/custom-webui/themes/${themePreference.value}/theme.css') layer(theme);` + themeStyle.textContent = `@layer theme { @import url('/custom-webui/themes/${themePreference.value}/theme.css'); }` } else { - themeStyle.textContent = `@import url('/theme.css') layer(theme);` + themeStyle.textContent = `@layer theme { @import url('/theme.css'); }` } } diff --git a/integration-tests/tests/cssClass/config.yaml b/integration-tests/tests/cssClass/config.yaml new file mode 100644 index 0000000..3e8cfc6 --- /dev/null +++ b/integration-tests/tests/cssClass/config.yaml @@ -0,0 +1,24 @@ +# +# Integration Test Config: cssClass on dashboard components (#804) +# + +listenAddressSingleHTTPFrontend: 0.0.0.0:1337 + +logLevel: "DEBUG" +checkForUpdates: false + +actions: [] + +dashboards: + - title: CssClass Dashboard + contents: + - title: Button with custom class + type: link + cssClass: test-custom-class + inlineAction: + shell: echo ok + icon: ping + - title: Display with custom class + type: display + cssClass: test-display-class + contents: [] diff --git a/integration-tests/tests/cssClass/cssClass.mjs b/integration-tests/tests/cssClass/cssClass.mjs new file mode 100644 index 0000000..444a6cb --- /dev/null +++ b/integration-tests/tests/cssClass/cssClass.mjs @@ -0,0 +1,66 @@ +import { describe, it, before, after, afterEach } from 'mocha' +import { expect } from 'chai' +import { By } from 'selenium-webdriver' +import { + getRootAndWait, + takeScreenshotOnFailure, +} from '../../lib/elements.js' + +describe('config: cssClass', function () { + before(async function () { + await runner.start('cssClass') + }) + + after(async () => { + await runner.stop() + }) + + afterEach(function () { + takeScreenshotOnFailure(this.currentTest, webdriver) + }) + + it('cssClass is applied to action button (link component)', async function () { + await getRootAndWait() + + const buttonWithClass = await webdriver.findElements(By.css('.action-button button.test-custom-class')) + expect(buttonWithClass).to.have.length.at.least(1, 'Action button should have cssClass test-custom-class on the button') + + const classAttr = await buttonWithClass[0].getAttribute('class') + expect(classAttr).to.include('test-custom-class') + }) + + it('cssClass override: style rule targeting custom class wins over component styles', async function () { + await getRootAndWait() + + const buttonWithClass = await webdriver.findElements(By.css('.action-button button.test-custom-class')) + expect(buttonWithClass).to.have.length.at.least(1) + + const beforePx = await buttonWithClass[0].getCssValue('border-top-width') + await webdriver.executeScript(` + var style = document.getElementById('cssclass-test-override-style'); + if (!style) { + style = document.createElement('style'); + style.id = 'cssclass-test-override-style'; + style.textContent = '.test-custom-class { border-top-width: 31px !important; }'; + document.head.appendChild(style); + } else { + style.textContent = '.test-custom-class { border-top-width: 31px !important; }'; + } + `) + await webdriver.sleep(150) + + const afterPx = await buttonWithClass[0].getCssValue('border-top-width') + const afterNum = parseInt(afterPx, 10) + expect(afterNum).to.be.greaterThan(10, 'Override targeting cssClass should win over component 1px (before=' + beforePx + ' after=' + afterPx + ') (#804)') + }) + + it('cssClass is applied to display component', async function () { + await getRootAndWait() + + const displayElements = await webdriver.findElements(By.css('.display.test-display-class')) + expect(displayElements).to.have.length.at.least(1, 'Display component with cssClass test-display-class should be in DOM') + + const classAttr = await displayElements[0].getAttribute('class') + expect(classAttr).to.include('test-display-class') + }) +})