[A/B-тестирование] Изменение родительского CSS-класса на основе переменной эксперимента

У нас есть существующая тема, над которой мы хотим проводить эксперименты. Например, мы хотим тестировать различные стили отображения списка тем. Для этого мы используем A/B-тестирование в Google Optimize. В настоящее время мы планируем показывать оригинальную тему 50% пользователей, а остальным — обновлённую тему. Оригинальная версия и вариант должны быть определены в рамках одной темы, так как мы планируем экспериментировать лишь с небольшой её частью. Проблема заключается в том, что родительские CSS-классы имеют одинаковые имена, и мы хотим применять стили к этим родительским классам на основе конкретной переменной. Как применить отдельные стили к разным шаблонам, если у них одинаковое имя родительского класса?

Ниже приведён упрощённый сценарий того, чего мы пытаемся достичь.

Предположим, что в моей теме стиль списка тем определён в файле common/common.scss следующим образом:

.topic-list {
  tbody {
    border: none;
  }
  tr {
    border: none;
  }
  .example-div {
    border: none;
  }
}

А в моём варианте я хочу установить следующее:

.topic-list {
  tbody {
    display: block;
    tr {
      display: block;
    }
  }
  .example-div {
    border: 2px solid black;
  }
  .another-example-div { 
    font-size: 10px;
  }
}

Файлы Handlebars, которые переопределяются, выглядят так:

javascripts/discourse/templates/list/custom-topic-list-item.hbr

<div class='example-div'>
  Hello
</div>

javascripts/discourse/templates/list/variant-custom-topic-list-item.hbr

<div class='example-div'>
  Hello,
  <div class='another-example-div'>
    Nice to meet you
  </div>
</div>

Класс topic-list упоминается в компоненте Discourse, но после рендеринга структура выглядит так:

<table class="topic-list">
  <tr class="topic-list-item">
   <div class='example-div'>
      Hello
    </div>
  </tr>
</table>

Я хочу рендерить один из этих файлов в зависимости от ID пользователя в моём заголовке:
common/header.html

const { findRawTemplate } = require("discourse-common/lib/raw-templates");
const user_id = api.getCurrentUser().id
 
api.modifyClass('component:topic-list-item', {
  renderTopicListItem() {
    let template = findRawTemplate("list/custom-topic-list-item");
    if (user_id % 2 === 0)
       template = findRawTemplate("list/variant-custom-topic-list-item");
    if (template) {
      this.set("topicListItemContents", template(this).htmlSafe());
    }
  },
});

Спасибо

Обратите внимание, что это, вероятно, вызывает необработанную ошибку у пользователей, которые не вошли в систему. Я рекомендую изменить это на

const user = api.getCurrentUser();
if (!user) return;

const userId = user.id;

Теперь, когда это решено, вернёмся к вашему вопросу.

В отличие от CSS, SCSS поддерживает селектор родителя, так что вы можете использовать его.

Поскольку вы хотите применять это к каждому второму пользователю (id % 2),

вы можете проверить это при загрузке приложения. Например, что-то вроде этого в самом верху:

const user = api.getCurrentUser();
const userId = user.id;

if (!userId) return;

const testThemeUser = userId % 2 === 0;

if (testThemeUser) {
  document.body.classList.add("test-theme");
}

Затем вы можете использовать testThemeUser в условных выражениях вашего JS и класс CSS test-theme в ваших стилях.

Например, что-то вроде этого.

JS

let template = findRawTemplate("list/custom-topic-list-item");
if (testThemeUser)
  template = findRawTemplate("list/variant-custom-topic-list-item");
if (template) {
  this.set("topicListItemContents", template(this).htmlSafe());
}

SCSS

.topic-list {
  tbody {
    border: none;
  }
  tr {
    border: none;
  }
  .example-div {
    border: none;
  }
  // стили для тестовой темы
  .test-theme & {
    .example-div {
      border: 2px solid black;
    }
    .another-example-div {
      font-size: 10px;
    }
  }
}

Спасибо за ваш ответ, @Johani.
На данный момент мне нужно обновить только конкретный класс компонента topic-list, поэтому я использую этот подход:

const useTestTheme = userId % 2 === 0

api.modifyClass('component:topic-list', {
    classNameBindings: ["test"],
    test: useTestTheme,
})